Merge pull request #686 from schungx/master

Small refinements.
This commit is contained in:
Stephen Chung 2022-12-27 23:10:35 +08:00 committed by GitHub
commit 8805f02a8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 680 additions and 557 deletions

View File

@ -10,6 +10,8 @@ Bug fixes
* Integer numbers that are too large to deserialize into `INT` now fall back to `Decimal` or `FLOAT` instead of silently truncating. * Integer numbers that are too large to deserialize into `INT` now fall back to `Decimal` or `FLOAT` instead of silently truncating.
* Parsing deeply-nested closures (e.g. `||{||{||{||{||{||{||{...}}}}}}}`) no longer panics but will be confined to the nesting limit. * Parsing deeply-nested closures (e.g. `||{||{||{||{||{||{||{...}}}}}}}`) no longer panics but will be confined to the nesting limit.
* Closures containing a single expression are now allowed in `Engine::eval_expression` etc. * Closures containing a single expression are now allowed in `Engine::eval_expression` etc.
* Strings interpolation now works under `Engine::new_raw` without any standard package.
* `Fn` now throws an error if the name is a reserved keyword as it cannot possibly map to such a function. This also disallows creating function pointers to custom operators which are defined as disabled keywords (a mouthful), but such custom operators are designed primarily to be used as operators.
Breaking API changes Breaking API changes
-------------------- --------------------
@ -57,8 +59,10 @@ Net features
### Enhanced array API ### Enhanced array API
* Array methods that take a function pointer, usually a closure (e.g. `map`, `filter`, `index_of` etc.), can now provide a closure with one few parameter but binds the first parameter to `this`. * Array methods that take a function pointer, usually a closure (e.g. `map`, `filter`, `index_of`, `reduce` etc.), can now bind the array element to `this` when calling a closure.
* This vastly improves performance when working with arrays of object maps by avoiding unnecessary cloning of large types. * This vastly improves performance when working with arrays of large types (e.g. object maps) by avoiding unnecessary cloning.
* `find` and `find_map` are added for arrays.
* `for_each` is also added for arrays, allowing a closure to mutate array elements (bound to `this`) in turn.
Enhancements Enhancements
------------ ------------
@ -77,7 +81,6 @@ Enhancements
* `FnPtr::iter_curry` and `FnPtr::iter_curry_mut` are added. * `FnPtr::iter_curry` and `FnPtr::iter_curry_mut` are added.
* `Dynamic::deep_scan` is added to recursively scan for `Dynamic` values. * `Dynamic::deep_scan` is added to recursively scan for `Dynamic` values.
* `>>` and `<<` operators on integers no longer throw errors when the number of bits to shift is out of bounds. Shifting by a negative number of bits simply reverses the shift direction. * `>>` and `<<` operators on integers no longer throw errors when the number of bits to shift is out of bounds. Shifting by a negative number of bits simply reverses the shift direction.
* `find` and `find_map` are added for arrays.
Version 1.11.0 Version 1.11.0

View File

@ -4,7 +4,7 @@
use crate::ast::Expr; use crate::ast::Expr;
use crate::func::SendSync; use crate::func::SendSync;
use crate::parser::ParseResult; use crate::parser::ParseResult;
use crate::tokenizer::{is_valid_identifier, Token, NO_TOKEN}; use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_identifier, Token};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult, Dynamic, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult,
@ -231,11 +231,11 @@ impl Engine {
continue; continue;
} }
let token = Token::lookup_symbol_from_syntax(s).unwrap_or_else(|| { let token = Token::lookup_symbol_from_syntax(s).or_else(|| {
if Token::is_reserved_keyword(s) { if is_reserved_keyword_or_symbol(s) {
Token::Reserved(Box::new(s.into())) Some(Token::Reserved(Box::new(s.into())))
} else { } else {
NO_TOKEN None
} }
}); });
@ -256,13 +256,13 @@ impl Engine {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(), CUSTOM_SYNTAX_MARKER_FLOAT if !segments.is_empty() => s.into(),
// Standard or reserved keyword/symbol not in first position // Standard or reserved keyword/symbol not in first position
_ if !segments.is_empty() && token != NO_TOKEN => { _ if !segments.is_empty() && token.is_some() => {
// Make it a custom keyword/symbol if it is disabled or reserved // Make it a custom keyword/symbol if it is disabled or reserved
if (self if (self
.disabled_symbols .disabled_symbols
.as_deref() .as_deref()
.map_or(false, |m| m.contains(s)) .map_or(false, |m| m.contains(s))
|| token.is_reserved()) || token.as_ref().map_or(false, Token::is_reserved))
&& !self && !self
.custom_keywords .custom_keywords
.as_deref() .as_deref()
@ -276,7 +276,7 @@ impl Engine {
} }
// Standard keyword in first position but not disabled // Standard keyword in first position but not disabled
_ if segments.is_empty() _ if segments.is_empty()
&& token.is_standard_keyword() && token.as_ref().map_or(false, Token::is_standard_keyword)
&& !self && !self
.disabled_symbols .disabled_symbols
.as_deref() .as_deref()
@ -298,7 +298,7 @@ impl Engine {
.disabled_symbols .disabled_symbols
.as_deref() .as_deref()
.map_or(false, |m| m.contains(s)) .map_or(false, |m| m.contains(s))
|| (token.is_reserved() || (token.as_ref().map_or(false, Token::is_reserved)
&& !self && !self
.custom_keywords .custom_keywords
.as_deref() .as_deref()

View File

@ -3,7 +3,7 @@
use super::{ASTFlags, ASTNode, Ident, Namespace, Stmt, StmtBlock}; use super::{ASTFlags, ASTNode, Ident, Namespace, Stmt, StmtBlock};
use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE};
use crate::func::hashing::ALT_ZERO_HASH; use crate::func::hashing::ALT_ZERO_HASH;
use crate::tokenizer::{Token, NO_TOKEN}; use crate::tokenizer::Token;
use crate::types::dynamic::Union; use crate::types::dynamic::Union;
use crate::{ use crate::{
calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, SmartString, StaticVec, calc_fn_hash, Dynamic, FnPtr, Identifier, ImmutableString, Position, SmartString, StaticVec,
@ -207,8 +207,7 @@ pub struct FnCallExpr {
/// Does this function call capture the parent scope? /// Does this function call capture the parent scope?
pub capture_parent_scope: bool, pub capture_parent_scope: bool,
/// Is this function call a native operator? /// Is this function call a native operator?
/// Otherwise set to [`Token::NONE`]. pub op_token: Option<Token>,
pub op_token: Token,
} }
impl fmt::Debug for FnCallExpr { impl fmt::Debug for FnCallExpr {
@ -222,7 +221,7 @@ impl fmt::Debug for FnCallExpr {
ff.field("hash", &self.hashes) ff.field("hash", &self.hashes)
.field("name", &self.name) .field("name", &self.name)
.field("args", &self.args); .field("args", &self.args);
if self.op_token != NO_TOKEN { if self.op_token.is_some() {
ff.field("op_token", &self.op_token); ff.field("op_token", &self.op_token);
} }
if self.capture_parent_scope { if self.capture_parent_scope {
@ -582,7 +581,7 @@ impl Expr {
hashes: calc_fn_hash(None, f.fn_name(), 1).into(), hashes: calc_fn_hash(None, f.fn_name(), 1).into(),
args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(), args: once(Self::StringConstant(f.fn_name().into(), pos)).collect(),
capture_parent_scope: false, capture_parent_scope: false,
op_token: NO_TOKEN, op_token: None,
} }
.into(), .into(),
pos, pos,

View File

@ -52,11 +52,6 @@ pub const FN_ANONYMOUS: &str = "anon$";
/// function to compare two [`Dynamic`] values. /// function to compare two [`Dynamic`] values.
pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax(); pub const OP_EQUALS: &str = Token::EqualsTo.literal_syntax();
/// Standard concatenation operator.
///
/// Used primarily to build up interpolated strings.
pub const OP_CONCAT: &str = Token::PlusAssign.literal_syntax();
/// Standard containment testing function. /// Standard containment testing function.
/// ///
/// The `in` operator is implemented as a call to this function. /// The `in` operator is implemented as a call to this function.

View File

@ -5,7 +5,6 @@ use super::{Caches, GlobalRuntimeState, Target};
use crate::ast::{ASTFlags, BinaryExpr, Expr, OpAssignment}; use crate::ast::{ASTFlags, BinaryExpr, Expr, OpAssignment};
use crate::config::hashing::SusLock; use crate::config::hashing::SusLock;
use crate::engine::{FN_IDX_GET, FN_IDX_SET}; use crate::engine::{FN_IDX_GET, FN_IDX_SET};
use crate::tokenizer::NO_TOKEN;
use crate::types::dynamic::Union; use crate::types::dynamic::Union;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR, calc_fn_hash, Dynamic, Engine, FnArgsVec, Position, RhaiResult, RhaiResultOf, Scope, ERR,
@ -73,7 +72,7 @@ impl Engine {
let hash = hash_idx().0; let hash = hash_idx().0;
let args = &mut [target, idx]; let args = &mut [target, idx];
self.exec_native_fn_call(global, caches, FN_IDX_GET, NO_TOKEN, hash, args, true, pos) self.exec_native_fn_call(global, caches, FN_IDX_GET, None, hash, args, true, pos)
.map(|(r, ..)| r) .map(|(r, ..)| r)
} }
@ -95,7 +94,7 @@ impl Engine {
let args = &mut [target, idx, new_val]; let args = &mut [target, idx, new_val];
self.exec_native_fn_call( self.exec_native_fn_call(
global, caches, FN_IDX_SET, NO_TOKEN, hash, args, is_ref_mut, pos, global, caches, FN_IDX_SET, None, hash, args, is_ref_mut, pos,
) )
} }
@ -751,8 +750,7 @@ impl Engine {
let (mut orig_val, ..) = self let (mut orig_val, ..) = self
.exec_native_fn_call( .exec_native_fn_call(
global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut, global, caches, getter, None, *hash_get, args, is_ref_mut, *pos,
*pos,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -786,7 +784,7 @@ impl Engine {
let args = &mut [target.as_mut(), &mut new_val]; let args = &mut [target.as_mut(), &mut new_val];
self.exec_native_fn_call( self.exec_native_fn_call(
global, caches, setter, NO_TOKEN, *hash_set, args, is_ref_mut, *pos, global, caches, setter, None, *hash_set, args, is_ref_mut, *pos,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -813,7 +811,7 @@ impl Engine {
let args = &mut [target.as_mut()]; let args = &mut [target.as_mut()];
self.exec_native_fn_call( self.exec_native_fn_call(
global, caches, getter, NO_TOKEN, *hash_get, args, is_ref_mut, *pos, global, caches, getter, None, *hash_get, args, is_ref_mut, *pos,
) )
.map_or_else( .map_or_else(
|err| match *err { |err| match *err {
@ -904,8 +902,8 @@ impl Engine {
// Assume getters are always pure // Assume getters are always pure
let (mut val, ..) = self let (mut val, ..) = self
.exec_native_fn_call( .exec_native_fn_call(
global, caches, getter, NO_TOKEN, *hash_get, args, global, caches, getter, None, *hash_get, args, is_ref_mut,
is_ref_mut, pos, pos,
) )
.or_else(|err| match *err { .or_else(|err| match *err {
// Try an indexer if property does not exist // Try an indexer if property does not exist
@ -940,7 +938,7 @@ impl Engine {
// The return value is thrown away and not used. // The return value is thrown away and not used.
let _ = self let _ = self
.exec_native_fn_call( .exec_native_fn_call(
global, caches, setter, NO_TOKEN, *hash_set, args, global, caches, setter, None, *hash_set, args,
is_ref_mut, pos, is_ref_mut, pos,
) )
.or_else(|err| match *err { .or_else(|err| match *err {

View File

@ -9,18 +9,18 @@ use std::borrow::Borrow;
use std::prelude::v1::*; use std::prelude::v1::*;
impl Dynamic { impl Dynamic {
/// Recursively calculate the sizes of a value. /// Recursively calculate the sizes of an array.
/// ///
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`. /// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if any interior data is shared (should never happen). /// Panics if any interior data is shared (should never happen).
pub(crate) fn calc_data_sizes(&self, _top: bool) -> (usize, usize, usize) {
match self.0 {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Union::Array(ref arr, ..) => { #[inline]
arr.iter() pub(crate) fn calc_array_sizes(array: &crate::Array, _top: bool) -> (usize, usize, usize) {
array
.iter()
.fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 {
Union::Array(..) => { Union::Array(..) => {
let (a, m, s) = value.calc_data_sizes(false); let (a, m, s) = value.calc_data_sizes(false);
@ -36,10 +36,16 @@ impl Dynamic {
_ => (ax + 1, mx, sx), _ => (ax + 1, mx, sx),
}) })
} }
#[cfg(not(feature = "no_index"))] /// Recursively calculate the sizes of a map.
Union::Blob(ref blob, ..) => (blob.len(), 0, 0), ///
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
///
/// # Panics
///
/// Panics if any interior data is shared (should never happen).
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Union::Map(ref map, ..) => { #[inline]
pub(crate) fn calc_map_sizes(map: &crate::Map, _top: bool) -> (usize, usize, usize) {
map.values() map.values()
.fold((0, 0, 0), |(ax, mx, sx), value| match value.0 { .fold((0, 0, 0), |(ax, mx, sx), value| match value.0 {
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
@ -57,6 +63,22 @@ impl Dynamic {
_ => (ax, mx + 1, sx), _ => (ax, mx + 1, sx),
}) })
} }
/// Recursively calculate the sizes of a value.
///
/// Sizes returned are `(` [`Array`][crate::Array], [`Map`][crate::Map] and [`String`] `)`.
///
/// # Panics
///
/// Panics if any interior data is shared (should never happen).
#[inline]
pub(crate) fn calc_data_sizes(&self, _top: bool) -> (usize, usize, usize) {
match self.0 {
#[cfg(not(feature = "no_index"))]
Union::Array(ref arr, ..) => Self::calc_array_sizes(&**arr, _top),
#[cfg(not(feature = "no_index"))]
Union::Blob(ref blob, ..) => (blob.len(), 0, 0),
#[cfg(not(feature = "no_object"))]
Union::Map(ref map, ..) => Self::calc_map_sizes(&**map, _top),
Union::Str(ref s, ..) => (0, 0, s.len()), Union::Str(ref s, ..) => (0, 0, s.len()),
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Union::Shared(..) if _top => self.read_lock::<Self>().unwrap().calc_data_sizes(true), Union::Shared(..) if _top => self.read_lock::<Self>().unwrap().calc_data_sizes(true),

View File

@ -1,13 +1,14 @@
//! Module defining functions for evaluating an expression. //! Module defining functions for evaluating an expression.
use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use super::{Caches, EvalContext, GlobalRuntimeState, Target};
use crate::ast::{Expr, OpAssignment}; use crate::ast::Expr;
use crate::engine::{KEYWORD_THIS, OP_CONCAT}; use crate::engine::KEYWORD_THIS;
use crate::packages::string_basic::{print_with_func, FUNC_TO_STRING};
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, ERR}; use crate::{Dynamic, Engine, RhaiResult, RhaiResultOf, Scope, SmartString, ERR};
use std::num::NonZeroUsize;
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
use std::{fmt::Write, num::NonZeroUsize};
impl Engine { impl Engine {
/// Search for a module within an imports stack. /// Search for a module within an imports stack.
@ -283,42 +284,43 @@ impl Engine {
// `... ${...} ...` // `... ${...} ...`
Expr::InterpolatedString(x, _) => { Expr::InterpolatedString(x, _) => {
let mut concat = self.const_empty_string().into(); let mut concat = SmartString::new_const();
let target = &mut concat;
let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE); x.iter().try_for_each(|expr| -> RhaiResultOf<()> {
let item = &mut self
x.iter()
.try_for_each(|expr| {
let item = self
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)?
.flatten(); .flatten();
let pos = expr.position();
op_info.pos = expr.start_position(); if item.is_string() {
write!(concat, "{item}").unwrap();
} else {
let source = global.source();
let context = &(self, FUNC_TO_STRING, source, &*global, pos).into();
let display = print_with_func(FUNC_TO_STRING, context, item);
write!(concat, "{}", display).unwrap();
}
self.eval_op_assignment(global, caches, &op_info, expr, target, item) #[cfg(not(feature = "unchecked"))]
}) self.throw_on_size((0, 0, concat.len()))
.map(|_| concat.take_or_clone()) .map_err(|err| err.fill_position(pos))?;
.and_then(|r| self.check_data_size(r, expr.start_position()))
Ok(())
})?;
Ok(self.get_interned_string(concat).into())
} }
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Expr::Array(x, ..) => { Expr::Array(x, ..) => {
let mut array = crate::Array::with_capacity(x.len());
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
let mut total_data_sizes = (0, 0, 0); let mut total_data_sizes = (0, 0, 0);
x.iter() x.iter().try_for_each(|item_expr| -> RhaiResultOf<()> {
.try_fold(
crate::Array::with_capacity(x.len()),
|mut array, item_expr| {
let value = self let value = self
.eval_expr( .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), item_expr)?
global,
caches,
scope,
this_ptr.as_deref_mut(),
item_expr,
)?
.flatten(); .flatten();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
@ -326,7 +328,7 @@ impl Engine {
let val_sizes = value.calc_data_sizes(true); let val_sizes = value.calc_data_sizes(true);
total_data_sizes = ( total_data_sizes = (
total_data_sizes.0 + val_sizes.0, total_data_sizes.0 + val_sizes.0 + 1,
total_data_sizes.1 + val_sizes.1, total_data_sizes.1 + val_sizes.1,
total_data_sizes.2 + val_sizes.2, total_data_sizes.2 + val_sizes.2,
); );
@ -336,19 +338,21 @@ impl Engine {
array.push(value); array.push(value);
Ok(array) Ok(())
}, })?;
)
.map(Into::into) Ok(Dynamic::from_array(array))
} }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(x, ..) => { Expr::Map(x, ..) => {
let mut map = x.1.clone();
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
let mut total_data_sizes = (0, 0, 0); let mut total_data_sizes = (0, 0, 0);
x.0.iter() x.0.iter()
.try_fold(x.1.clone(), |mut map, (key, value_expr)| { .try_for_each(|(key, value_expr)| -> RhaiResultOf<()> {
let value = self let value = self
.eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)? .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), value_expr)?
.flatten(); .flatten();
@ -358,7 +362,7 @@ impl Engine {
let delta = value.calc_data_sizes(true); let delta = value.calc_data_sizes(true);
total_data_sizes = ( total_data_sizes = (
total_data_sizes.0 + delta.0, total_data_sizes.0 + delta.0,
total_data_sizes.1 + delta.1, total_data_sizes.1 + delta.1 + 1,
total_data_sizes.2 + delta.2, total_data_sizes.2 + delta.2,
); );
self.throw_on_size(total_data_sizes) self.throw_on_size(total_data_sizes)
@ -367,9 +371,10 @@ impl Engine {
*map.get_mut(key.as_str()).unwrap() = value; *map.get_mut(key.as_str()).unwrap() = value;
Ok(map) Ok(())
}) })?;
.map(Into::into)
Ok(Dynamic::from_map(map))
} }
Expr::And(x, ..) => Ok((self Expr::And(x, ..) => Ok((self

View File

@ -140,15 +140,16 @@ impl Engine {
let args = &mut [&mut *lock_guard, &mut new_val]; let args = &mut [&mut *lock_guard, &mut new_val];
if self.fast_operators() { if self.fast_operators() {
if let Some((func, ctx)) = if let Some((func, need_context)) =
get_builtin_op_assignment_fn(op_assign_token.clone(), args[0], args[1]) get_builtin_op_assignment_fn(op_assign_token.clone(), args[0], args[1])
{ {
// Built-in found // Built-in found
let op = op_assign_token.literal_syntax(); let op = op_assign_token.literal_syntax();
auto_restore! { let orig_level = global.level; global.level += 1 } auto_restore! { let orig_level = global.level; global.level += 1 }
let context = if ctx { let context = if need_context {
Some((self, op, None, &*global, *op_pos).into()) let source = global.source();
Some((self, op, source, &*global, *op_pos).into())
} else { } else {
None None
}; };
@ -158,7 +159,7 @@ impl Engine {
let op_assign = op_assign_token.literal_syntax(); let op_assign = op_assign_token.literal_syntax();
let op = op_token.literal_syntax(); let op = op_token.literal_syntax();
let token = op_assign_token.clone(); let token = Some(op_assign_token.clone());
match self match self
.exec_native_fn_call(global, caches, op_assign, token, hash, args, true, *op_pos) .exec_native_fn_call(global, caches, op_assign, token, hash, args, true, *op_pos)
@ -167,7 +168,7 @@ impl Engine {
Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) => Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) =>
{ {
// Expand to `var = var op rhs` // Expand to `var = var op rhs`
let token = op_token.clone(); let token = Some(op_token.clone());
*args[0] = self *args[0] = self
.exec_native_fn_call( .exec_native_fn_call(

View File

@ -91,65 +91,65 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
let type2 = y.type_id(); let type2 = y.type_id();
macro_rules! impl_op { macro_rules! impl_op {
($xx:ident $op:tt $yy:ident) => { (|_, args| { ($xx:ident $op:tt $yy:ident) => { Some((|_, args| {
let x = &*args[0].read_lock::<$xx>().unwrap(); let x = &*args[0].read_lock::<$xx>().unwrap();
let y = &*args[1].read_lock::<$yy>().unwrap(); let y = &*args[1].read_lock::<$yy>().unwrap();
Ok((x $op y).into()) Ok((x $op y).into())
}, false) }; }, false)) };
($xx:ident . $func:ident ( $yy:ty )) => { (|_, args| { ($xx:ident . $func:ident ( $yy:ty )) => { Some((|_, args| {
let x = &*args[0].read_lock::<$xx>().unwrap(); let x = &*args[0].read_lock::<$xx>().unwrap();
let y = &*args[1].read_lock::<$yy>().unwrap(); let y = &*args[1].read_lock::<$yy>().unwrap();
Ok(x.$func(y).into()) Ok(x.$func(y).into())
}, false) }; }, false)) };
($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { (|_, args| { ($xx:ident . $func:ident ( $yy:ident . $yyy:ident () )) => { Some((|_, args| {
let x = &*args[0].read_lock::<$xx>().unwrap(); let x = &*args[0].read_lock::<$xx>().unwrap();
let y = &*args[1].read_lock::<$yy>().unwrap(); let y = &*args[1].read_lock::<$yy>().unwrap();
Ok(x.$func(y.$yyy()).into()) Ok(x.$func(y.$yyy()).into())
}, false) }; }, false)) };
($func:ident ( $op:tt )) => { (|_, args| { ($func:ident ( $op:tt )) => { Some((|_, args| {
let (x, y) = $func(args); let (x, y) = $func(args);
Ok((x $op y).into()) Ok((x $op y).into())
}, false) }; }, false)) };
($base:ty => $xx:ident $op:tt $yy:ident) => { (|_, args| { ($base:ty => $xx:ident $op:tt $yy:ident) => { 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())
}, false) }; }, false)) };
($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { (|_, args| { ($base:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty)) => { 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())
}, false) }; }, false)) };
($base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| { ($base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { 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($func(x, y).into()) Ok($func(x, y).into())
}, false) }; }, false)) };
($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| { ($base:ty => $func:ident ( $xx:ident, $yy:ident )) => { 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).map(Into::into) $func(x, y).map(Into::into)
}, false) }; }, false)) };
(from $base:ty => $xx:ident $op:tt $yy:ident) => { (|_, args| { (from $base:ty => $xx:ident $op:tt $yy:ident) => { 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())
}, false) }; }, false)) };
(from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { (|_, args| { (from $base:ty => $xx:ident . $func:ident ( $yy:ident )) => { 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())
}, false) }; }, false)) };
(from $base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| { (from $base:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { 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($func(x, y).into()) Ok($func(x, y).into())
}, false) }; }, false)) };
(from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| { (from $base:ty => $func:ident ( $xx:ident, $yy:ident )) => { 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).map(Into::into) $func(x, y).map(Into::into)
}, false) }; }, false)) };
} }
// Check for common patterns // Check for common patterns
@ -161,25 +161,25 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
match op { match op {
Plus => return Some(impl_op!(INT => add(as_int, as_int))), Plus => return impl_op!(INT => add(as_int, as_int)),
Minus => return Some(impl_op!(INT => subtract(as_int, as_int))), Minus => return impl_op!(INT => subtract(as_int, as_int)),
Multiply => return Some(impl_op!(INT => multiply(as_int, as_int))), Multiply => return impl_op!(INT => multiply(as_int, as_int)),
Divide => return Some(impl_op!(INT => divide(as_int, as_int))), Divide => return impl_op!(INT => divide(as_int, as_int)),
Modulo => return Some(impl_op!(INT => modulo(as_int, as_int))), Modulo => return impl_op!(INT => modulo(as_int, as_int)),
PowerOf => return Some(impl_op!(INT => power(as_int, as_int))), PowerOf => return impl_op!(INT => power(as_int, as_int)),
RightShift => return Some(impl_op!(INT => Ok(shift_right(as_int, as_int)))), RightShift => return impl_op!(INT => Ok(shift_right(as_int, as_int))),
LeftShift => return Some(impl_op!(INT => Ok(shift_left(as_int, as_int)))), LeftShift => return impl_op!(INT => Ok(shift_left(as_int, as_int))),
_ => (), _ => (),
} }
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
match op { match op {
Plus => return Some(impl_op!(INT => as_int + as_int)), Plus => return impl_op!(INT => as_int + as_int),
Minus => return Some(impl_op!(INT => as_int - as_int)), Minus => return impl_op!(INT => as_int - as_int),
Multiply => return Some(impl_op!(INT => as_int * as_int)), Multiply => return impl_op!(INT => as_int * as_int),
Divide => return Some(impl_op!(INT => as_int / as_int)), Divide => return impl_op!(INT => as_int / as_int),
Modulo => return Some(impl_op!(INT => as_int % as_int)), Modulo => return impl_op!(INT => as_int % as_int),
PowerOf => return Some(impl_op!(INT => as_int.pow(as_int as u32))), PowerOf => return impl_op!(INT => as_int.pow(as_int as u32)),
RightShift => { RightShift => {
return Some(( return Some((
|_, args| { |_, args| {
@ -204,32 +204,32 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
} }
return match op { return match op {
EqualsTo => Some(impl_op!(INT => as_int == as_int)), EqualsTo => impl_op!(INT => as_int == as_int),
NotEqualsTo => Some(impl_op!(INT => as_int != as_int)), NotEqualsTo => impl_op!(INT => as_int != as_int),
GreaterThan => Some(impl_op!(INT => as_int > as_int)), GreaterThan => impl_op!(INT => as_int > as_int),
GreaterThanEqualsTo => Some(impl_op!(INT => as_int >= as_int)), GreaterThanEqualsTo => impl_op!(INT => as_int >= as_int),
LessThan => Some(impl_op!(INT => as_int < as_int)), LessThan => impl_op!(INT => as_int < as_int),
LessThanEqualsTo => Some(impl_op!(INT => as_int <= as_int)), LessThanEqualsTo => impl_op!(INT => as_int <= as_int),
Ampersand => Some(impl_op!(INT => as_int & as_int)), Ampersand => impl_op!(INT => as_int & as_int),
Pipe => Some(impl_op!(INT => as_int | as_int)), Pipe => impl_op!(INT => as_int | as_int),
XOr => Some(impl_op!(INT => as_int ^ as_int)), XOr => impl_op!(INT => as_int ^ as_int),
ExclusiveRange => Some(impl_op!(INT => as_int .. as_int)), ExclusiveRange => impl_op!(INT => as_int .. as_int),
InclusiveRange => Some(impl_op!(INT => as_int ..= as_int)), InclusiveRange => impl_op!(INT => as_int ..= as_int),
_ => None, _ => None,
}; };
} }
if type1 == TypeId::of::<bool>() { if type1 == TypeId::of::<bool>() {
return match op { return match op {
EqualsTo => Some(impl_op!(bool => as_bool == as_bool)), EqualsTo => impl_op!(bool => as_bool == as_bool),
NotEqualsTo => Some(impl_op!(bool => as_bool != as_bool)), NotEqualsTo => impl_op!(bool => as_bool != as_bool),
GreaterThan => Some(impl_op!(bool => as_bool > as_bool)), GreaterThan => impl_op!(bool => as_bool > as_bool),
GreaterThanEqualsTo => Some(impl_op!(bool => as_bool >= as_bool)), GreaterThanEqualsTo => impl_op!(bool => as_bool >= as_bool),
LessThan => Some(impl_op!(bool => as_bool < as_bool)), LessThan => impl_op!(bool => as_bool < as_bool),
LessThanEqualsTo => Some(impl_op!(bool => as_bool <= as_bool)), LessThanEqualsTo => impl_op!(bool => as_bool <= as_bool),
Ampersand => Some(impl_op!(bool => as_bool & as_bool)), Ampersand => impl_op!(bool => as_bool & as_bool),
Pipe => Some(impl_op!(bool => as_bool | as_bool)), Pipe => impl_op!(bool => as_bool | as_bool),
XOr => Some(impl_op!(bool => as_bool ^ as_bool)), XOr => impl_op!(bool => as_bool ^ as_bool),
_ => None, _ => None,
}; };
} }
@ -250,13 +250,13 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
}, },
CHECKED_BUILD, CHECKED_BUILD,
)), )),
Minus => Some(impl_op!(ImmutableString - ImmutableString)), Minus => impl_op!(ImmutableString - ImmutableString),
EqualsTo => Some(impl_op!(ImmutableString == ImmutableString)), EqualsTo => impl_op!(ImmutableString == ImmutableString),
NotEqualsTo => Some(impl_op!(ImmutableString != ImmutableString)), NotEqualsTo => impl_op!(ImmutableString != ImmutableString),
GreaterThan => Some(impl_op!(ImmutableString > ImmutableString)), GreaterThan => impl_op!(ImmutableString > ImmutableString),
GreaterThanEqualsTo => Some(impl_op!(ImmutableString >= ImmutableString)), GreaterThanEqualsTo => impl_op!(ImmutableString >= ImmutableString),
LessThan => Some(impl_op!(ImmutableString < ImmutableString)), LessThan => impl_op!(ImmutableString < ImmutableString),
LessThanEqualsTo => Some(impl_op!(ImmutableString <= ImmutableString)), LessThanEqualsTo => impl_op!(ImmutableString <= ImmutableString),
_ => None, _ => None,
}; };
} }
@ -279,12 +279,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
}, },
CHECKED_BUILD, CHECKED_BUILD,
)), )),
EqualsTo => Some(impl_op!(char => as_char == as_char)), EqualsTo => impl_op!(char => as_char == as_char),
NotEqualsTo => Some(impl_op!(char => as_char != as_char)), NotEqualsTo => impl_op!(char => as_char != as_char),
GreaterThan => Some(impl_op!(char => as_char > as_char)), GreaterThan => impl_op!(char => as_char > as_char),
GreaterThanEqualsTo => Some(impl_op!(char => as_char >= as_char)), GreaterThanEqualsTo => impl_op!(char => as_char >= as_char),
LessThan => Some(impl_op!(char => as_char < as_char)), LessThan => impl_op!(char => as_char < as_char),
LessThanEqualsTo => Some(impl_op!(char => as_char <= as_char)), LessThanEqualsTo => impl_op!(char => as_char <= as_char),
_ => None, _ => None,
}; };
} }
@ -316,8 +316,8 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
}, },
CHECKED_BUILD, CHECKED_BUILD,
)), )),
EqualsTo => Some(impl_op!(Blob == Blob)), EqualsTo => impl_op!(Blob == Blob),
NotEqualsTo => Some(impl_op!(Blob != Blob)), NotEqualsTo => impl_op!(Blob != Blob),
_ => None, _ => None,
}; };
} }
@ -338,18 +338,18 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
($x:ty, $xx:ident, $y:ty, $yy:ident) => { ($x:ty, $xx:ident, $y:ty, $yy:ident) => {
if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
return match op { return match op {
Plus => Some(impl_op!(FLOAT => $xx + $yy)), Plus => impl_op!(FLOAT => $xx + $yy),
Minus => Some(impl_op!(FLOAT => $xx - $yy)), Minus => impl_op!(FLOAT => $xx - $yy),
Multiply => Some(impl_op!(FLOAT => $xx * $yy)), Multiply => impl_op!(FLOAT => $xx * $yy),
Divide => Some(impl_op!(FLOAT => $xx / $yy)), Divide => impl_op!(FLOAT => $xx / $yy),
Modulo => Some(impl_op!(FLOAT => $xx % $yy)), Modulo => impl_op!(FLOAT => $xx % $yy),
PowerOf => Some(impl_op!(FLOAT => $xx.powf($yy as FLOAT))), PowerOf => impl_op!(FLOAT => $xx.powf($yy as FLOAT)),
EqualsTo => Some(impl_op!(FLOAT => $xx == $yy)), EqualsTo => impl_op!(FLOAT => $xx == $yy),
NotEqualsTo => Some(impl_op!(FLOAT => $xx != $yy)), NotEqualsTo => impl_op!(FLOAT => $xx != $yy),
GreaterThan => Some(impl_op!(FLOAT => $xx > $yy)), GreaterThan => impl_op!(FLOAT => $xx > $yy),
GreaterThanEqualsTo => Some(impl_op!(FLOAT => $xx >= $yy)), GreaterThanEqualsTo => impl_op!(FLOAT => $xx >= $yy),
LessThan => Some(impl_op!(FLOAT => $xx < $yy)), LessThan => impl_op!(FLOAT => $xx < $yy),
LessThanEqualsTo => Some(impl_op!(FLOAT => $xx <= $yy)), LessThanEqualsTo => impl_op!(FLOAT => $xx <= $yy),
_ => None, _ => None,
}; };
} }
@ -373,12 +373,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
match op { match op {
Plus => return Some(impl_op!(from Decimal => add($xx, $yy))), Plus => return impl_op!(from Decimal => add($xx, $yy)),
Minus => return Some(impl_op!(from Decimal => subtract($xx, $yy))), Minus => return impl_op!(from Decimal => subtract($xx, $yy)),
Multiply => return Some(impl_op!(from Decimal => multiply($xx, $yy))), Multiply => return impl_op!(from Decimal => multiply($xx, $yy)),
Divide => return Some(impl_op!(from Decimal => divide($xx, $yy))), Divide => return impl_op!(from Decimal => divide($xx, $yy)),
Modulo => return Some(impl_op!(from Decimal => modulo($xx, $yy))), Modulo => return impl_op!(from Decimal => modulo($xx, $yy)),
PowerOf => return Some(impl_op!(from Decimal => power($xx, $yy))), PowerOf => return impl_op!(from Decimal => power($xx, $yy)),
_ => () _ => ()
} }
@ -387,22 +387,22 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
match op { match op {
Plus => return Some(impl_op!(from Decimal => $xx + $yy)), Plus => return impl_op!(from Decimal => $xx + $yy),
Minus => return Some(impl_op!(from Decimal => $xx - $yy)), Minus => return impl_op!(from Decimal => $xx - $yy),
Multiply => return Some(impl_op!(from Decimal => $xx * $yy)), Multiply => return impl_op!(from Decimal => $xx * $yy),
Divide => return Some(impl_op!(from Decimal => $xx / $yy)), Divide => return impl_op!(from Decimal => $xx / $yy),
Modulo => return Some(impl_op!(from Decimal => $xx % $yy)), Modulo => return impl_op!(from Decimal => $xx % $yy),
PowerOf => return Some(impl_op!(from Decimal => $xx.powd($yy))), PowerOf => return impl_op!(from Decimal => $xx.powd($yy)),
_ => () _ => ()
} }
return match op { return match op {
EqualsTo => Some(impl_op!(from Decimal => $xx == $yy)), EqualsTo => impl_op!(from Decimal => $xx == $yy),
NotEqualsTo => Some(impl_op!(from Decimal => $xx != $yy)), NotEqualsTo => impl_op!(from Decimal => $xx != $yy),
GreaterThan => Some(impl_op!(from Decimal => $xx > $yy)), GreaterThan => impl_op!(from Decimal => $xx > $yy),
GreaterThanEqualsTo => Some(impl_op!(from Decimal => $xx >= $yy)), GreaterThanEqualsTo => impl_op!(from Decimal => $xx >= $yy),
LessThan => Some(impl_op!(from Decimal => $xx < $yy)), LessThan => impl_op!(from Decimal => $xx < $yy),
LessThanEqualsTo => Some(impl_op!(from Decimal => $xx <= $yy)), LessThanEqualsTo => impl_op!(from Decimal => $xx <= $yy),
_ => None _ => None
}; };
} }
@ -444,12 +444,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
}, },
CHECKED_BUILD, CHECKED_BUILD,
)), )),
EqualsTo => Some(impl_op!(get_s1s2(==))), EqualsTo => impl_op!(get_s1s2(==)),
NotEqualsTo => Some(impl_op!(get_s1s2(!=))), NotEqualsTo => impl_op!(get_s1s2(!=)),
GreaterThan => Some(impl_op!(get_s1s2(>))), GreaterThan => impl_op!(get_s1s2(>)),
GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))), GreaterThanEqualsTo => impl_op!(get_s1s2(>=)),
LessThan => Some(impl_op!(get_s1s2(<))), LessThan => impl_op!(get_s1s2(<)),
LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))), LessThanEqualsTo => impl_op!(get_s1s2(<=)),
_ => None, _ => None,
}; };
} }
@ -486,12 +486,12 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
}, },
false, false,
)), )),
EqualsTo => Some(impl_op!(get_s1s2(==))), EqualsTo => impl_op!(get_s1s2(==)),
NotEqualsTo => Some(impl_op!(get_s1s2(!=))), NotEqualsTo => impl_op!(get_s1s2(!=)),
GreaterThan => Some(impl_op!(get_s1s2(>))), GreaterThan => impl_op!(get_s1s2(>)),
GreaterThanEqualsTo => Some(impl_op!(get_s1s2(>=))), GreaterThanEqualsTo => impl_op!(get_s1s2(>=)),
LessThan => Some(impl_op!(get_s1s2(<))), LessThan => impl_op!(get_s1s2(<)),
LessThanEqualsTo => Some(impl_op!(get_s1s2(<=))), LessThanEqualsTo => impl_op!(get_s1s2(<=)),
_ => None, _ => None,
}; };
} }
@ -568,16 +568,16 @@ pub fn get_builtin_binary_op_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Option<F
// Handle ranges here because ranges are implemented as custom type // Handle ranges here because ranges are implemented as custom type
if type1 == TypeId::of::<ExclusiveRange>() && type1 == type2 { if type1 == TypeId::of::<ExclusiveRange>() && type1 == type2 {
return match op { return match op {
EqualsTo => Some(impl_op!(ExclusiveRange == ExclusiveRange)), EqualsTo => impl_op!(ExclusiveRange == ExclusiveRange),
NotEqualsTo => Some(impl_op!(ExclusiveRange != ExclusiveRange)), NotEqualsTo => impl_op!(ExclusiveRange != ExclusiveRange),
_ => None, _ => None,
}; };
} }
if type1 == TypeId::of::<InclusiveRange>() && type1 == type2 { if type1 == TypeId::of::<InclusiveRange>() && type1 == type2 {
return match op { return match op {
EqualsTo => Some(impl_op!(InclusiveRange == InclusiveRange)), EqualsTo => impl_op!(InclusiveRange == InclusiveRange),
NotEqualsTo => Some(impl_op!(InclusiveRange != InclusiveRange)), NotEqualsTo => impl_op!(InclusiveRange != InclusiveRange),
_ => None, _ => None,
}; };
} }
@ -626,54 +626,54 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
let type2 = y.type_id(); let type2 = y.type_id();
macro_rules! impl_op { macro_rules! impl_op {
($x:ty = x $op:tt $yy:ident) => { (|_, args| { ($x:ty = x $op:tt $yy:ident) => { 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;
Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() = x $op y).into())
}, false) }; }, false)) };
($x:ident $op:tt $yy:ident) => { (|_, args| { ($x:ident $op:tt $yy:ident) => { Some((|_, args| {
let y = args[1].$yy().unwrap() as $x; let y = args[1].$yy().unwrap() as $x;
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
}, false) }; }, false)) };
($x:ident $op:tt $yy:ident as $yyy:ty) => { (|_, args| { ($x:ident $op:tt $yy:ident as $yyy:ty) => { Some((|_, args| {
let y = args[1].$yy().unwrap() as $yyy; let y = args[1].$yy().unwrap() as $yyy;
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
}, false) }; }, false)) };
($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { (|_, args| { ($x:ty => $xx:ident . $func:ident ( $yy:ident as $yyy:ty )) => { 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())
}, false) }; }, false)) };
($x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| { ($x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { 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;
let v: Dynamic = $func(x, y).into(); let v: Dynamic = $func(x, y).into();
Ok((*args[0].write_lock().unwrap() = v).into()) Ok((*args[0].write_lock().unwrap() = v).into())
}, false) }; }, false)) };
($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| { ($x:ty => $func:ident ( $xx:ident, $yy:ident )) => { 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().unwrap() = $func(x, y)?).into()) Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
}, false) }; }, false)) };
(from $x:ident $op:tt $yy:ident) => { (|_, args| { (from $x:ident $op:tt $yy:ident) => { Some((|_, args| {
let y = <$x>::from(args[1].$yy().unwrap()); let y = <$x>::from(args[1].$yy().unwrap());
Ok((*args[0].write_lock::<$x>().unwrap() $op y).into()) Ok((*args[0].write_lock::<$x>().unwrap() $op y).into())
}, false) }; }, false)) };
(from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { (|_, args| { (from $x:ty => $xx:ident . $func:ident ( $yy:ident )) => { 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())
}, false) }; }, false)) };
(from $x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { (|_, args| { (from $x:ty => Ok($func:ident ( $xx:ident, $yy:ident ))) => { 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().unwrap() = $func(x, y).into()).into()) Ok((*args[0].write_lock().unwrap() = $func(x, y).into()).into())
}, false) }; }, false)) };
(from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { (|_, args| { (from $x:ty => $func:ident ( $xx:ident, $yy:ident )) => { 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().unwrap() = $func(x, y)?).into()) Ok((*args[0].write_lock().unwrap() = $func(x, y)?).into())
}, false) }; }, false)) };
} }
// Check for common patterns // Check for common patterns
@ -685,25 +685,25 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
match op { match op {
PlusAssign => return Some(impl_op!(INT => add(as_int, as_int))), PlusAssign => return impl_op!(INT => add(as_int, as_int)),
MinusAssign => return Some(impl_op!(INT => subtract(as_int, as_int))), MinusAssign => return impl_op!(INT => subtract(as_int, as_int)),
MultiplyAssign => return Some(impl_op!(INT => multiply(as_int, as_int))), MultiplyAssign => return impl_op!(INT => multiply(as_int, as_int)),
DivideAssign => return Some(impl_op!(INT => divide(as_int, as_int))), DivideAssign => return impl_op!(INT => divide(as_int, as_int)),
ModuloAssign => return Some(impl_op!(INT => modulo(as_int, as_int))), ModuloAssign => return impl_op!(INT => modulo(as_int, as_int)),
PowerOfAssign => return Some(impl_op!(INT => power(as_int, as_int))), PowerOfAssign => return impl_op!(INT => power(as_int, as_int)),
RightShiftAssign => return Some(impl_op!(INT => Ok(shift_right(as_int, as_int)))), RightShiftAssign => return impl_op!(INT => Ok(shift_right(as_int, as_int))),
LeftShiftAssign => return Some(impl_op!(INT => Ok(shift_left(as_int, as_int)))), LeftShiftAssign => return impl_op!(INT => Ok(shift_left(as_int, as_int))),
_ => (), _ => (),
} }
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
match op { match op {
PlusAssign => return Some(impl_op!(INT += as_int)), PlusAssign => return impl_op!(INT += as_int),
MinusAssign => return Some(impl_op!(INT -= as_int)), MinusAssign => return impl_op!(INT -= as_int),
MultiplyAssign => return Some(impl_op!(INT *= as_int)), MultiplyAssign => return impl_op!(INT *= as_int),
DivideAssign => return Some(impl_op!(INT /= as_int)), DivideAssign => return impl_op!(INT /= as_int),
ModuloAssign => return Some(impl_op!(INT %= as_int)), ModuloAssign => return impl_op!(INT %= as_int),
PowerOfAssign => return Some(impl_op!(INT => as_int.pow(as_int as u32))), PowerOfAssign => return impl_op!(INT => as_int.pow(as_int as u32)),
RightShiftAssign => { RightShiftAssign => {
return Some(( return Some((
|_, args| { |_, args| {
@ -730,17 +730,17 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
} }
return match op { return match op {
AndAssign => Some(impl_op!(INT &= as_int)), AndAssign => impl_op!(INT &= as_int),
OrAssign => Some(impl_op!(INT |= as_int)), OrAssign => impl_op!(INT |= as_int),
XOrAssign => Some(impl_op!(INT ^= as_int)), XOrAssign => impl_op!(INT ^= as_int),
_ => None, _ => None,
}; };
} }
if type1 == TypeId::of::<bool>() { if type1 == TypeId::of::<bool>() {
return match op { return match op {
AndAssign => Some(impl_op!(bool = x && as_bool)), AndAssign => impl_op!(bool = x && as_bool),
OrAssign => Some(impl_op!(bool = x || as_bool)), OrAssign => impl_op!(bool = x || as_bool),
_ => None, _ => None,
}; };
} }
@ -861,12 +861,12 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
($x:ident, $xx:ident, $y:ty, $yy:ident) => { ($x:ident, $xx:ident, $y:ty, $yy:ident) => {
if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) { if (type1, type2) == (TypeId::of::<$x>(), TypeId::of::<$y>()) {
return match op { return match op {
PlusAssign => Some(impl_op!($x += $yy)), PlusAssign => impl_op!($x += $yy),
MinusAssign => Some(impl_op!($x -= $yy)), MinusAssign => impl_op!($x -= $yy),
MultiplyAssign => Some(impl_op!($x *= $yy)), MultiplyAssign => impl_op!($x *= $yy),
DivideAssign => Some(impl_op!($x /= $yy)), DivideAssign => impl_op!($x /= $yy),
ModuloAssign => Some(impl_op!($x %= $yy)), ModuloAssign => impl_op!($x %= $yy),
PowerOfAssign => Some(impl_op!($x => $xx.powf($yy as $x))), PowerOfAssign => impl_op!($x => $xx.powf($yy as $x)),
_ => None, _ => None,
}; };
} }
@ -889,12 +889,12 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
return match op { return match op {
PlusAssign => Some(impl_op!(from $x => add($xx, $yy))), PlusAssign => impl_op!(from $x => add($xx, $yy)),
MinusAssign => Some(impl_op!(from $x => subtract($xx, $yy))), MinusAssign => impl_op!(from $x => subtract($xx, $yy)),
MultiplyAssign => Some(impl_op!(from $x => multiply($xx, $yy))), MultiplyAssign => impl_op!(from $x => multiply($xx, $yy)),
DivideAssign => Some(impl_op!(from $x => divide($xx, $yy))), DivideAssign => impl_op!(from $x => divide($xx, $yy)),
ModuloAssign => Some(impl_op!(from $x => modulo($xx, $yy))), ModuloAssign => impl_op!(from $x => modulo($xx, $yy)),
PowerOfAssign => Some(impl_op!(from $x => power($xx, $yy))), PowerOfAssign => impl_op!(from $x => power($xx, $yy)),
_ => None, _ => None,
}; };
@ -903,12 +903,12 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
return match op { return match op {
PlusAssign => Some(impl_op!(from $x += $yy)), PlusAssign => impl_op!(from $x += $yy),
MinusAssign => Some(impl_op!(from $x -= $yy)), MinusAssign => impl_op!(from $x -= $yy),
MultiplyAssign => Some(impl_op!(from $x *= $yy)), MultiplyAssign => impl_op!(from $x *= $yy),
DivideAssign => Some(impl_op!(from $x /= $yy)), DivideAssign => impl_op!(from $x /= $yy),
ModuloAssign => Some(impl_op!(from $x %= $yy)), ModuloAssign => impl_op!(from $x %= $yy),
PowerOfAssign => Some(impl_op!(from $x => $xx.powd($yy))), PowerOfAssign => impl_op!(from $x => $xx.powd($yy)),
_ => None, _ => None,
}; };
} }
@ -939,7 +939,7 @@ pub fn get_builtin_op_assignment_fn(op: Token, x: &Dynamic, y: &Dynamic) -> Opti
}, },
CHECKED_BUILD, CHECKED_BUILD,
)), )),
MinusAssign => Some(impl_op!(ImmutableString -= as_char as char)), MinusAssign => impl_op!(ImmutableString -= as_char as char),
_ => None, _ => None,
}; };
} }

View File

@ -8,7 +8,7 @@ use crate::engine::{
KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState};
use crate::tokenizer::{is_valid_function_name, Token, NO_TOKEN}; use crate::tokenizer::{is_valid_function_name, Token};
use crate::{ use crate::{
calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString, calc_fn_hash, calc_fn_hash_full, Dynamic, Engine, FnArgsVec, FnPtr, ImmutableString,
OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, Scope, Shared, ERR,
@ -164,7 +164,7 @@ impl Engine {
_global: &GlobalRuntimeState, _global: &GlobalRuntimeState,
caches: &'s mut Caches, caches: &'s mut Caches,
local_entry: &'s mut Option<FnResolutionCacheEntry>, local_entry: &'s mut Option<FnResolutionCacheEntry>,
op_token: Token, op_token: Option<Token>,
hash_base: u64, hash_base: u64,
args: Option<&mut FnCallArgs>, args: Option<&mut FnCallArgs>,
allow_dynamic: bool, allow_dynamic: bool,
@ -270,30 +270,29 @@ impl Engine {
} }
// Try to find a built-in version // Try to find a built-in version
let builtin = args.and_then(|args| match op_token { let builtin =
Token::NONE => None, args.and_then(|args| match op_token {
token if token.is_op_assignment() => { None => None,
Some(token) if token.is_op_assignment() => {
let (first_arg, rest_args) = args.split_first().unwrap(); let (first_arg, rest_args) = args.split_first().unwrap();
get_builtin_op_assignment_fn(token, first_arg, rest_args[0]).map( get_builtin_op_assignment_fn(token, first_arg, rest_args[0])
|(f, has_context)| FnResolutionCacheEntry { .map(|(f, has_context)| FnResolutionCacheEntry {
func: CallableFunction::Method { func: CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context, has_context,
}, },
source: None, source: None,
}, })
)
} }
token => get_builtin_binary_op_fn(token, args[0], args[1]).map( Some(token) => get_builtin_binary_op_fn(token, args[0], args[1])
|(f, has_context)| FnResolutionCacheEntry { .map(|(f, has_context)| FnResolutionCacheEntry {
func: CallableFunction::Method { func: CallableFunction::Method {
func: Shared::new(f), func: Shared::new(f),
has_context, has_context,
}, },
source: None, source: None,
}, }),
),
}); });
return if cache.filter.is_absent_and_set(hash) { return if cache.filter.is_absent_and_set(hash) {
@ -346,7 +345,7 @@ impl Engine {
global: &mut GlobalRuntimeState, global: &mut GlobalRuntimeState,
caches: &mut Caches, caches: &mut Caches,
name: &str, name: &str,
op_token: Token, op_token: Option<Token>,
hash: u64, hash: u64,
args: &mut FnCallArgs, args: &mut FnCallArgs,
is_ref_mut: bool, is_ref_mut: bool,
@ -568,21 +567,13 @@ impl Engine {
caches: &mut Caches, caches: &mut Caches,
_scope: Option<&mut Scope>, _scope: Option<&mut Scope>,
fn_name: &str, fn_name: &str,
op_token: Token, op_token: Option<Token>,
hashes: FnCallHashes, hashes: FnCallHashes,
mut _args: &mut FnCallArgs, mut _args: &mut FnCallArgs,
is_ref_mut: bool, is_ref_mut: bool,
_is_method_call: bool, _is_method_call: bool,
pos: Position, pos: Position,
) -> RhaiResultOf<(Dynamic, bool)> { ) -> RhaiResultOf<(Dynamic, bool)> {
fn no_method_err(name: &str, pos: Position) -> RhaiResultOf<(Dynamic, bool)> {
Err(ERR::ErrorRuntime(
format!("'{name}' should not be called this way. Try {name}(...);").into(),
pos,
)
.into())
}
// Check for data race. // Check for data race.
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
ensure_no_data_race(fn_name, _args, is_ref_mut)?; ensure_no_data_race(fn_name, _args, is_ref_mut)?;
@ -622,16 +613,13 @@ impl Engine {
// Handle is_shared() // Handle is_shared()
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
crate::engine::KEYWORD_IS_SHARED if _args.len() == 1 => { crate::engine::KEYWORD_IS_SHARED => {
return no_method_err(fn_name, pos) unreachable!("{} called as method", fn_name)
} }
KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR if _args.len() == 1 => { KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR | KEYWORD_FN_PTR_CALL
return no_method_err(fn_name, pos) | KEYWORD_FN_PTR_CURRY => {
} unreachable!("{} called as method", fn_name)
KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY if !_args.is_empty() => {
return no_method_err(fn_name, pos)
} }
_ => (), _ => (),
@ -645,7 +633,7 @@ impl Engine {
let local_entry = &mut None; let local_entry = &mut None;
if let Some(FnResolutionCacheEntry { func, ref source }) = self if let Some(FnResolutionCacheEntry { func, ref source }) = self
.resolve_fn(global, caches, local_entry, NO_TOKEN, hash, None, false) .resolve_fn(global, caches, local_entry, None, hash, None, false)
.cloned() .cloned()
{ {
// Script function call // Script function call
@ -812,7 +800,7 @@ impl Engine {
caches, caches,
None, None,
fn_name, fn_name,
NO_TOKEN, None,
new_hash, new_hash,
args, args,
false, false,
@ -899,7 +887,7 @@ impl Engine {
caches, caches,
None, None,
&fn_name, &fn_name,
NO_TOKEN, None,
new_hash, new_hash,
args, args,
is_ref_mut, is_ref_mut,
@ -986,7 +974,7 @@ impl Engine {
caches, caches,
None, None,
fn_name, fn_name,
NO_TOKEN, None,
hash, hash,
&mut args, &mut args,
is_ref_mut, is_ref_mut,
@ -1012,7 +1000,7 @@ impl Engine {
scope: &mut Scope, scope: &mut Scope,
mut this_ptr: Option<&mut Dynamic>, mut this_ptr: Option<&mut Dynamic>,
fn_name: &str, fn_name: &str,
op_token: Token, op_token: Option<Token>,
first_arg: Option<&Expr>, first_arg: Option<&Expr>,
args_expr: &[Expr], args_expr: &[Expr],
hashes: FnCallHashes, hashes: FnCallHashes,
@ -1028,7 +1016,7 @@ impl Engine {
let redirected; // Handle call() - Redirect function call let redirected; // Handle call() - Redirect function call
match name { match name {
_ if op_token != NO_TOKEN => (), _ if op_token.is_some() => (),
// Handle call(fn_ptr, ...) // Handle call(fn_ptr, ...)
KEYWORD_FN_PTR_CALL if total_args >= 1 => { KEYWORD_FN_PTR_CALL if total_args >= 1 => {
@ -1582,7 +1570,7 @@ impl Engine {
let op_token = op_token.clone(); let op_token = op_token.clone();
// Short-circuit native unary operator call if under Fast Operators mode // Short-circuit native unary operator call if under Fast Operators mode
if op_token == Token::Bang && self.fast_operators() && args.len() == 1 { if op_token == Some(Token::Bang) && self.fast_operators() && args.len() == 1 {
let mut value = self let mut value = self
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
.0 .0
@ -1598,7 +1586,7 @@ impl Engine {
} }
// Short-circuit native binary operator call if under Fast Operators mode // Short-circuit native binary operator call if under Fast Operators mode
if op_token != NO_TOKEN && self.fast_operators() && args.len() == 2 { if op_token.is_some() && self.fast_operators() && args.len() == 2 {
let mut lhs = self let mut lhs = self
.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])? .get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &args[0])?
.0 .0
@ -1611,13 +1599,13 @@ impl Engine {
let operands = &mut [&mut lhs, &mut rhs]; let operands = &mut [&mut lhs, &mut rhs];
if let Some((func, ctx)) = if let Some((func, need_context)) =
get_builtin_binary_op_fn(op_token.clone(), operands[0], operands[1]) get_builtin_binary_op_fn(op_token.clone().unwrap(), operands[0], operands[1])
{ {
// Built-in found // Built-in found
auto_restore! { let orig_level = global.level; global.level += 1 } auto_restore! { let orig_level = global.level; global.level += 1 }
let context = if ctx { let context = if need_context {
Some((self, name.as_str(), None, &*global, pos).into()) Some((self, name.as_str(), None, &*global, pos).into())
} else { } else {
None None

View File

@ -4,7 +4,7 @@ use super::call::FnCallArgs;
use crate::ast::FnCallHashes; use crate::ast::FnCallHashes;
use crate::eval::{Caches, GlobalRuntimeState}; use crate::eval::{Caches, GlobalRuntimeState};
use crate::plugin::PluginFunction; use crate::plugin::PluginFunction;
use crate::tokenizer::{is_valid_function_name, Token, TokenizeState, NO_TOKEN}; use crate::tokenizer::{is_valid_function_name, Token, TokenizeState};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, RhaiResultOf, calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, RhaiResultOf,
@ -436,7 +436,7 @@ impl<'a> NativeCallContext<'a> {
let caches = &mut Caches::new(); let caches = &mut Caches::new();
let fn_name = fn_name.as_ref(); let fn_name = fn_name.as_ref();
let op_token = Token::lookup_symbol_from_syntax(fn_name).unwrap_or(NO_TOKEN); let op_token = Token::lookup_symbol_from_syntax(fn_name);
let args_len = args.len(); let args_len = args.len();
if native_only { if native_only {

View File

@ -147,7 +147,7 @@ impl<'a> OptimizerState<'a> {
pub fn call_fn_with_constant_arguments( pub fn call_fn_with_constant_arguments(
&mut self, &mut self,
fn_name: &str, fn_name: &str,
op_token: Token, op_token: Option<Token>,
arg_values: &mut [Dynamic], arg_values: &mut [Dynamic],
) -> Option<Dynamic> { ) -> Option<Dynamic> {
self.engine self.engine
@ -1137,8 +1137,8 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) {
return; return;
} }
// Overloaded operators can override built-in. // Overloaded operators can override built-in.
_ if x.args.len() == 2 && x.op_token != Token::NONE && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => { _ if x.args.len() == 2 && x.op_token.is_some() && (state.engine.fast_operators() || !state.engine.has_native_fn_override(x.hashes.native(), &arg_types)) => {
if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone(), &arg_values[0], &arg_values[1]) if let Some(result) = get_builtin_binary_op_fn(x.op_token.clone().unwrap(), &arg_values[0], &arg_values[1])
.and_then(|(f, ctx)| { .and_then(|(f, ctx)| {
let context = if ctx { let context = if ctx {
Some((state.engine, x.name.as_str(), None, &state.global, *pos).into()) Some((state.engine, x.name.as_str(), None, &state.global, *pos).into())

View File

@ -235,52 +235,15 @@ pub mod array_functions {
// Check if array will be over max size limit // Check if array will be over max size limit
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ if _ctx.engine().max_array_size() > 0 {
use crate::types::dynamic::Union; let pad = len - array.len();
let (a, m, s) = Dynamic::calc_array_sizes(array, true);
let (ax, mx, sx) = item.calc_data_sizes(true);
if _ctx.engine().max_array_size() > 0 && len > _ctx.engine().max_array_size() { _ctx.engine()
return Err( .throw_on_size((a + pad + ax * pad, m + mx * pad, s + sx * pad))?;
ERR::ErrorDataTooLarge("Size of array".to_string(), Position::NONE).into(),
);
} }
let check_sizes = match item.0 {
Union::Str(..) => true,
Union::Array(..) => true,
#[cfg(not(feature = "no_object"))]
Union::Map(..) => true,
_ => false,
};
if check_sizes {
let mut arr_len = array.len();
let mut arr = Dynamic::from_array(mem::take(array));
let (mut a1, mut m1, mut s1) = arr.calc_data_sizes(true);
let (a2, m2, s2) = item.calc_data_sizes(true);
{
let mut guard = arr.write_lock::<Array>().unwrap();
while arr_len < len {
a1 += a2;
m1 += m2;
s1 += s2;
_ctx.engine().throw_on_size((a1, m1, s1))?;
guard.push(item.clone());
arr_len += 1;
}
}
*array = arr.into_array().unwrap();
} else {
array.resize(len, item);
}
}
#[cfg(feature = "unchecked")]
array.resize(len, item); array.resize(len, item);
Ok(()) Ok(())
@ -644,6 +607,43 @@ pub mod array_functions {
result result
} }
} }
/// Iterate through all the elements in the array, applying a `process` function to each element in turn.
/// Each element is bound to `this` before calling the function.
///
/// # Function Parameters
///
/// * `this`: bound to array element (mutable)
/// * `index` _(optional)_: current index in the array
///
/// # Example
///
/// ```rhai
/// let x = [1, 2, 3, 4, 5];
///
/// x.for_each(|| this *= this);
///
/// print(x); // prints "[1, 4, 9, 16, 25]"
///
/// x.for_each(|i| this *= i);
///
/// print(x); // prints "[0, 2, 6, 12, 20]"
/// ```
#[rhai_fn(return_raw)]
pub fn for_each(ctx: NativeCallContext, array: &mut Array, map: FnPtr) -> RhaiResultOf<()> {
if array.is_empty() {
return Ok(());
}
for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()];
let _ = map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, None)?;
}
Ok(())
}
/// Iterate through all the elements in the array, applying a `mapper` function to each element /// Iterate through all the elements in the array, applying a `mapper` function to each element
/// in turn, and return the results as a new array. /// in turn, and return the results as a new array.
/// ///
@ -679,8 +679,7 @@ pub mod array_functions {
for (i, item) in array.iter_mut().enumerate() { for (i, item) in array.iter_mut().enumerate() {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex, Some(0))?);
ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex)?);
} }
Ok(ar) Ok(ar)
@ -723,7 +722,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("filter", &ctx, Some(item), [], ex)? .call_raw_with_extra_args("filter", &ctx, Some(item), [], ex, Some(0))?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -962,7 +961,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex)? .call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex, Some(0))?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1128,7 +1127,8 @@ pub mod array_functions {
for (i, item) in array.iter_mut().enumerate().skip(start) { for (i, item) in array.iter_mut().enumerate().skip(start) {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
let value = filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex)?; let value =
filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex, Some(0))?;
if !value.is_unit() { if !value.is_unit() {
return Ok(value); return Ok(value);
@ -1169,7 +1169,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("some", &ctx, Some(item), [], ex)? .call_raw_with_extra_args("some", &ctx, Some(item), [], ex, Some(0))?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1211,7 +1211,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if !filter if !filter
.call_raw_with_extra_args("all", &ctx, Some(item), [], ex)? .call_raw_with_extra_args("all", &ctx, Some(item), [], ex, Some(0))?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1334,11 +1334,11 @@ pub mod array_functions {
} }
array array
.iter() .iter_mut()
.enumerate() .enumerate()
.try_fold(initial, |result, (i, item)| { .try_fold(initial, |result, (i, item)| {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
reducer.call_raw_with_extra_args("reduce", &ctx, None, [result, item.clone()], ex) reducer.call_raw_with_extra_args("reduce", &ctx, Some(item), [result], ex, Some(1))
}) })
} }
/// Reduce an array by iterating through all elements, in _reverse_ order, /// Reduce an array by iterating through all elements, in _reverse_ order,
@ -1400,19 +1400,22 @@ pub mod array_functions {
return Ok(initial); return Ok(initial);
} }
let len = array.len();
array array
.iter() .iter_mut()
.rev() .rev()
.enumerate() .enumerate()
.try_fold(initial, |result, (i, item)| { .try_fold(initial, |result, (i, item)| {
let ex = [((array.len() - 1 - i) as INT).into()]; let ex = [((len - 1 - i) as INT).into()];
reducer.call_raw_with_extra_args( reducer.call_raw_with_extra_args(
"reduce_rev", "reduce_rev",
&ctx, &ctx,
None, Some(item),
[result, item.clone()], [result],
ex, ex,
Some(1),
) )
}) })
} }
@ -1602,7 +1605,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex)? .call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex, Some(0))?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {
@ -1749,7 +1752,7 @@ pub mod array_functions {
let ex = [(i as INT).into()]; let ex = [(i as INT).into()];
if filter if filter
.call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex)? .call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex, Some(0))?
.as_bool() .as_bool()
.unwrap_or(false) .unwrap_or(false)
{ {

View File

@ -44,7 +44,14 @@ pub fn print_with_func(
result.into_immutable_string().expect("`ImmutableString`") result.into_immutable_string().expect("`ImmutableString`")
} }
Ok(result) => ctx.engine().map_type_name(result.type_name()).into(), Ok(result) => ctx.engine().map_type_name(result.type_name()).into(),
Err(_) => ctx.engine().map_type_name(value.type_name()).into(), Err(_) => {
let mut buf = SmartString::new_const();
match fn_name {
FUNC_TO_DEBUG => write!(&mut buf, "{value:?}").unwrap(),
_ => write!(&mut buf, "{value}").unwrap(),
}
ctx.engine().map_type_name(&buf).into()
}
} }
} }
@ -58,7 +65,9 @@ mod print_debug_functions {
/// Convert the value of the `item` into a string. /// Convert the value of the `item` into a string.
#[rhai_fn(name = "to_string", pure)] #[rhai_fn(name = "to_string", pure)]
pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString { pub fn to_string_generic(ctx: NativeCallContext, item: &mut Dynamic) -> ImmutableString {
ctx.engine().map_type_name(&item.to_string()).into() let mut buf = SmartString::new_const();
write!(&mut buf, "{item}").unwrap();
ctx.engine().map_type_name(&buf).into()
} }
/// Convert the value of the `item` into a string in debug format. /// Convert the value of the `item` into a string in debug format.
#[rhai_fn(name = "debug", pure)] #[rhai_fn(name = "debug", pure)]
@ -95,7 +104,9 @@ mod print_debug_functions {
/// Return the character into a string. /// Return the character into a string.
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_char(character: char) -> ImmutableString { pub fn print_char(character: char) -> ImmutableString {
character.to_string().into() let mut buf = SmartString::new_const();
buf.push(character);
buf.into()
} }
/// Convert the string into debug format. /// Convert the string into debug format.
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
@ -108,13 +119,17 @@ mod print_debug_functions {
/// Convert the function pointer into a string in debug format. /// Convert the function pointer into a string in debug format.
#[rhai_fn(name = "debug", name = "to_debug", pure)] #[rhai_fn(name = "debug", name = "to_debug", pure)]
pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString { pub fn debug_fn_ptr(f: &mut FnPtr) -> ImmutableString {
f.to_string().into() let mut buf = SmartString::new_const();
write!(&mut buf, "{f}").unwrap();
buf.into()
} }
/// Return the boolean value into a string. /// Return the boolean value into a string.
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_bool(value: bool) -> ImmutableString { pub fn print_bool(value: bool) -> ImmutableString {
value.to_string().into() let mut buf = SmartString::new_const();
write!(&mut buf, "{value}").unwrap();
buf.into()
} }
/// Convert the boolean value into a string in debug format. /// Convert the boolean value into a string in debug format.
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
@ -141,30 +156,32 @@ mod print_debug_functions {
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_f64(number: f64) -> ImmutableString { pub fn print_f64(number: f64) -> ImmutableString {
crate::types::FloatWrapper::new(number).to_string().into() let mut buf = SmartString::new_const();
write!(&mut buf, "{}", crate::types::FloatWrapper::new(number)).unwrap();
buf.into()
} }
/// Convert the value of `number` into a string. /// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "print", name = "to_string")] #[rhai_fn(name = "print", name = "to_string")]
pub fn print_f32(number: f32) -> ImmutableString { pub fn print_f32(number: f32) -> ImmutableString {
crate::types::FloatWrapper::new(number).to_string().into() let mut buf = SmartString::new_const();
write!(&mut buf, "{}", crate::types::FloatWrapper::new(number)).unwrap();
buf.into()
} }
/// Convert the value of `number` into a string. /// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f64(number: f64) -> ImmutableString { pub fn debug_f64(number: f64) -> ImmutableString {
let number = crate::types::FloatWrapper::new(number);
let mut buf = SmartString::new_const(); let mut buf = SmartString::new_const();
write!(&mut buf, "{number:?}").unwrap(); write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap();
buf.into() buf.into()
} }
/// Convert the value of `number` into a string. /// Convert the value of `number` into a string.
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
#[rhai_fn(name = "debug", name = "to_debug")] #[rhai_fn(name = "debug", name = "to_debug")]
pub fn debug_f32(number: f32) -> ImmutableString { pub fn debug_f32(number: f32) -> ImmutableString {
let number = crate::types::FloatWrapper::new(number);
let mut buf = SmartString::new_const(); let mut buf = SmartString::new_const();
write!(&mut buf, "{number:?}").unwrap(); write!(&mut buf, "{:?}", crate::types::FloatWrapper::new(number)).unwrap();
buf.into() buf.into()
} }
@ -179,7 +196,7 @@ mod print_debug_functions {
)] )]
pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString { pub fn format_array(ctx: NativeCallContext, array: &mut Array) -> ImmutableString {
let len = array.len(); let len = array.len();
let mut result = String::with_capacity(len * 5 + 2); let mut result = SmartString::new_const();
result.push('['); result.push('[');
array.iter_mut().enumerate().for_each(|(i, x)| { array.iter_mut().enumerate().for_each(|(i, x)| {
@ -204,12 +221,10 @@ mod print_debug_functions {
)] )]
pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString { pub fn format_map(ctx: NativeCallContext, map: &mut Map) -> ImmutableString {
let len = map.len(); let len = map.len();
let mut result = String::with_capacity(len * 5 + 3); let mut result = SmartString::new_const();
result.push_str("#{"); result.push_str("#{");
map.iter_mut().enumerate().for_each(|(i, (k, v))| { map.iter_mut().enumerate().for_each(|(i, (k, v))| {
use std::fmt::Write;
write!( write!(
result, result,
"{:?}: {}{}", "{:?}: {}{}",

View File

@ -12,7 +12,7 @@ use crate::eval::{Caches, GlobalRuntimeState};
use crate::func::{hashing::get_hasher, StraightHashMap}; use crate::func::{hashing::get_hasher, StraightHashMap};
use crate::tokenizer::{ use crate::tokenizer::{
is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream,
TokenizerControl, NO_TOKEN, TokenizerControl,
}; };
use crate::types::dynamic::AccessMode; use crate::types::dynamic::AccessMode;
use crate::types::StringsInterner; use crate::types::StringsInterner;
@ -627,7 +627,7 @@ impl Engine {
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_interned_string(id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
op_token: NO_TOKEN, op_token: None,
namespace: _namespace, namespace: _namespace,
hashes, hashes,
args, args,
@ -702,7 +702,7 @@ impl Engine {
return Ok(FnCallExpr { return Ok(FnCallExpr {
name: state.get_interned_string(id), name: state.get_interned_string(id),
capture_parent_scope, capture_parent_scope,
op_token: NO_TOKEN, op_token: None,
namespace: _namespace, namespace: _namespace,
hashes, hashes,
args, args,
@ -1657,7 +1657,7 @@ impl Engine {
match input.peek().expect(NEVER_ENDS).0 { match input.peek().expect(NEVER_ENDS).0 {
// Function call is allowed to have reserved keyword // Function call is allowed to have reserved keyword
Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s).0 => {
Expr::Variable( Expr::Variable(
(None, ns, 0, state.get_interned_string(*s)).into(), (None, ns, 0, state.get_interned_string(*s)).into(),
None, None,
@ -1800,7 +1800,10 @@ impl Engine {
state.allow_capture = false; state.allow_capture = false;
} }
} }
(Token::Reserved(s), ..) if is_keyword_function(s) => (), (Token::Reserved(s), ..) if is_keyword_function(s).1 => (),
(Token::Reserved(s), pos) => {
return Err(PERR::Reserved(s.to_string()).into_err(*pos))
}
(.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)), (.., pos) => return Err(PERR::PropertyExpected.into_err(*pos)),
} }
@ -1924,7 +1927,7 @@ impl Engine {
name: state.get_interned_string("-"), name: state.get_interned_string("-"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "-", 1)),
args, args,
op_token: token, op_token: Some(token),
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1952,7 +1955,7 @@ impl Engine {
name: state.get_interned_string("+"), name: state.get_interned_string("+"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "+", 1)),
args, args,
op_token: token, op_token: Some(token),
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1973,7 +1976,7 @@ impl Engine {
name: state.get_interned_string("!"), name: state.get_interned_string("!"),
hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, "!", 1)),
args, args,
op_token: token, op_token: Some(token),
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos)) .into_fn_call_expr(pos))
@ -1987,7 +1990,7 @@ impl Engine {
/// Make an assignment statement. /// Make an assignment statement.
fn make_assignment_stmt( fn make_assignment_stmt(
op: Token, op: Option<Token>,
state: &mut ParseState, state: &mut ParseState,
lhs: Expr, lhs: Expr,
rhs: Expr, rhs: Expr,
@ -2020,10 +2023,10 @@ impl Engine {
} }
} }
let op_info = if op == NO_TOKEN { let op_info = if let Some(op) = op {
OpAssignment::new_assignment(op_pos)
} else {
OpAssignment::new_op_assignment_from_token(op, op_pos) OpAssignment::new_op_assignment_from_token(op, op_pos)
} else {
OpAssignment::new_assignment(op_pos)
}; };
match lhs { match lhs {
@ -2109,9 +2112,7 @@ impl Engine {
} }
// lhs.module::id - syntax error // lhs.module::id - syntax error
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
(.., Expr::Variable(x, ..)) if !x.1.is_empty() => { (.., Expr::Variable(x, ..)) if !x.1.is_empty() => unreachable!("lhs.ns::id"),
Err(PERR::PropertyExpected.into_err(x.1.position()))
}
// lhs.id // lhs.id
(lhs, var_expr @ Expr::Variable(..)) => { (lhs, var_expr @ Expr::Variable(..)) => {
let rhs = var_expr.into_property(state); let rhs = var_expr.into_property(state);
@ -2125,9 +2126,7 @@ impl Engine {
)), )),
// lhs.nnn::func(...) - syntax error // lhs.nnn::func(...) - syntax error
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
(.., Expr::FnCall(f, ..)) if f.is_qualified() => { (.., Expr::FnCall(f, ..)) if f.is_qualified() => unreachable!("lhs.ns::func()"),
Err(PERR::PropertyExpected.into_err(f.namespace.position()))
}
// lhs.Fn() or lhs.eval() // lhs.Fn() or lhs.eval()
(.., Expr::FnCall(f, func_pos)) (.., Expr::FnCall(f, func_pos))
if f.args.is_empty() if f.args.is_empty()
@ -2174,13 +2173,11 @@ impl Engine {
match x.lhs { match x.lhs {
// lhs.module::id.dot_rhs or lhs.module::id[idx_rhs] - syntax error // lhs.module::id.dot_rhs or lhs.module::id[idx_rhs] - syntax error
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Expr::Variable(x, ..) if !x.1.is_empty() => { Expr::Variable(x, ..) if !x.1.is_empty() => unreachable!("lhs.ns::id..."),
Err(PERR::PropertyExpected.into_err(x.1.position()))
}
// lhs.module::func().dot_rhs or lhs.module::func()[idx_rhs] - syntax error // lhs.module::func().dot_rhs or lhs.module::func()[idx_rhs] - syntax error
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Expr::FnCall(f, ..) if f.is_qualified() => { Expr::FnCall(f, ..) if f.is_qualified() => {
Err(PERR::PropertyExpected.into_err(f.namespace.position())) unreachable!("lhs.ns::func()...")
} }
// lhs.id.dot_rhs or lhs.id[idx_rhs] // lhs.id.dot_rhs or lhs.id[idx_rhs]
Expr::Variable(..) | Expr::Property(..) => { Expr::Variable(..) | Expr::Property(..) => {
@ -2310,9 +2307,9 @@ impl Engine {
let hash = calc_fn_hash(None, &op, 2); let hash = calc_fn_hash(None, &op, 2);
let is_valid_script_function = is_valid_function_name(&op); let is_valid_script_function = is_valid_function_name(&op);
let operator_token = if is_valid_script_function { let operator_token = if is_valid_script_function {
NO_TOKEN None
} else { } else {
op_token.clone() Some(op_token.clone())
}; };
let mut args = StaticVec::new_const(); let mut args = StaticVec::new_const();
@ -2383,7 +2380,7 @@ impl Engine {
name: state.get_interned_string(op), name: state.get_interned_string(op),
hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1)), hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1)),
args, args,
op_token: Token::Bang, op_token: Some(Token::Bang),
capture_parent_scope: false, capture_parent_scope: false,
}; };
not_base.into_fn_call_expr(pos) not_base.into_fn_call_expr(pos)
@ -3186,11 +3183,12 @@ impl Engine {
let (op, pos) = match input.peek().expect(NEVER_ENDS) { let (op, pos) = match input.peek().expect(NEVER_ENDS) {
// var = ... // var = ...
(Token::Equals, ..) => (NO_TOKEN, eat_token(input, Token::Equals)), (Token::Equals, ..) => (None, eat_token(input, Token::Equals)),
// var op= ... // var op= ...
(token, ..) if token.is_op_assignment() => { (token, ..) if token.is_op_assignment() => input
input.next().map(|(op, pos)| (op, pos)).expect(NEVER_ENDS) .next()
} .map(|(op, pos)| (Some(op), pos))
.expect(NEVER_ENDS),
// Not op-assignment // Not op-assignment
_ => return Ok(Stmt::Expr(expr.into())), _ => return Ok(Stmt::Expr(expr.into())),
}; };
@ -3686,7 +3684,7 @@ impl Engine {
num_externals + 1, num_externals + 1,
)), )),
args, args,
op_token: NO_TOKEN, op_token: None,
capture_parent_scope: false, capture_parent_scope: false,
} }
.into_fn_call_expr(pos); .into_fn_call_expr(pos);

View File

@ -55,9 +55,6 @@ type LERR = LexError;
/// Separator character for numbers. /// Separator character for numbers.
const NUMBER_SEPARATOR: char = '_'; const NUMBER_SEPARATOR: char = '_';
/// No token.
pub const NO_TOKEN: Token = Token::NONE;
/// A stream of tokens. /// A stream of tokens.
pub type TokenStream<'a> = Peekable<TokenIterator<'a>>; pub type TokenStream<'a> = Peekable<TokenIterator<'a>>;
@ -631,41 +628,6 @@ impl Token {
}) })
} }
/// Is a piece of syntax a reserved keyword?
#[must_use]
pub fn is_reserved_keyword(syntax: &str) -> bool {
match syntax {
#[cfg(feature = "no_object")]
"?." => true,
#[cfg(feature = "no_index")]
"?[" => true,
#[cfg(feature = "no_function")]
"fn" | "private" => true,
#[cfg(feature = "no_module")]
"import" | "export" | "as" => true,
// List of reserved operators
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
// List of reserved keywords
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case"
| "default" | "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync"
| "async" | "await" | "yield" => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => {
true
}
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN => true,
_ => false,
}
}
/// If another operator is after these, it's probably a unary operator /// If another operator is after these, it's probably a unary operator
/// (not sure about `fn` name). /// (not sure about `fn` name).
#[must_use] #[must_use]
@ -1994,7 +1956,8 @@ fn parse_identifier_token(
if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) { if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) {
return (token, start_pos); return (token, start_pos);
} }
if Token::is_reserved_keyword(&identifier) {
if is_reserved_keyword_or_symbol(&identifier) {
return (Token::Reserved(Box::new(identifier)), start_pos); return (Token::Reserved(Box::new(identifier)), start_pos);
} }
@ -2008,18 +1971,27 @@ fn parse_identifier_token(
(Token::Identifier(identifier.into()), start_pos) (Token::Identifier(identifier.into()), start_pos)
} }
/// Is a keyword allowed as a function? /// Can a keyword be called like a function?
///
/// # Return values
///
/// The first `bool` indicates whether the keyword can be called normally as a function.
///
/// The second `bool` indicates whether the keyword can be called in method-call style.
#[inline] #[inline]
#[must_use] #[must_use]
pub fn is_keyword_function(name: &str) -> bool { pub fn is_keyword_function(name: &str) -> (bool, bool) {
match name { match name {
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR KEYWORD_TYPE_OF | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => (true, true),
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_IS_DEF_VAR => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_EVAL | KEYWORD_FN_PTR | KEYWORD_IS_DEF_VAR => {
(true, false)
}
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN => true, crate::engine::KEYWORD_IS_DEF_FN => (true, false),
_ => false, _ => (false, false),
} }
} }
@ -2047,7 +2019,9 @@ pub fn is_valid_identifier(name: &str) -> bool {
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn is_valid_function_name(name: &str) -> bool { pub fn is_valid_function_name(name: &str) -> bool {
is_valid_identifier(name) && !is_keyword_function(name) is_valid_identifier(name)
&& !is_reserved_keyword_or_symbol(name)
&& Token::lookup_symbol_from_syntax(name).is_none()
} }
/// Is a character valid to start an identifier? /// Is a character valid to start an identifier?
@ -2082,6 +2056,39 @@ pub const fn is_id_continue(x: char) -> bool {
x.is_ascii_alphanumeric() || x == '_' x.is_ascii_alphanumeric() || x == '_'
} }
/// Is a piece of syntax a reserved keyword or symbol?
#[must_use]
pub fn is_reserved_keyword_or_symbol(syntax: &str) -> bool {
match syntax {
#[cfg(feature = "no_object")]
"?." => true,
#[cfg(feature = "no_index")]
"?[" => true,
#[cfg(feature = "no_function")]
"fn" | "private" => true,
#[cfg(feature = "no_module")]
"import" | "export" | "as" => true,
// List of reserved operators
"===" | "!==" | "->" | "<-" | "?" | ":=" | ":;" | "~" | "!." | "::<" | "(*" | "*)"
| "#" | "#!" | "@" | "$" | "++" | "--" | "..." | "<|" | "|>" => true,
// List of reserved keywords
"public" | "protected" | "super" | "new" | "use" | "module" | "package" | "var"
| "static" | "shared" | "with" | "is" | "goto" | "exit" | "match" | "case" | "default"
| "void" | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await"
| "yield" => true,
KEYWORD_PRINT | KEYWORD_DEBUG | KEYWORD_TYPE_OF | KEYWORD_EVAL | KEYWORD_FN_PTR
| KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY | KEYWORD_THIS | KEYWORD_IS_DEF_VAR => true,
#[cfg(not(feature = "no_function"))]
crate::engine::KEYWORD_IS_DEF_FN => true,
_ => false,
}
}
/// _(internals)_ A type that implements the [`InputStream`] trait. /// _(internals)_ A type that implements the [`InputStream`] trait.
/// Exported under the `internals` feature only. /// Exported under the `internals` feature only.
/// ///

View File

@ -2,11 +2,11 @@
use crate::eval::GlobalRuntimeState; use crate::eval::GlobalRuntimeState;
use crate::func::EncapsulatedEnviron; use crate::func::EncapsulatedEnviron;
use crate::tokenizer::is_valid_function_name; use crate::tokenizer::{is_reserved_keyword_or_symbol, is_valid_function_name, Token};
use crate::types::dynamic::Variant; use crate::types::dynamic::Variant;
use crate::{ use crate::{
Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, Position, RhaiError, Dynamic, Engine, FnArgsVec, FuncArgs, ImmutableString, NativeCallContext, ParseErrorType,
RhaiResult, RhaiResultOf, Shared, StaticVec, AST, ERR, Position, RhaiError, RhaiResult, RhaiResultOf, Shared, StaticVec, AST, ERR,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -360,7 +360,9 @@ impl FnPtr {
/// arguments attached. /// arguments attached.
/// ///
/// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`. /// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`.
/// When an appropriate function is not found, it is then removed and mapped to the first parameter. ///
/// When an appropriate function is not found and `move_this_ptr_to_args` is `Some`, `this_ptr`
/// is removed and inserted as the appropriate parameter number.
/// ///
/// This is useful for calling predicate closures within an iteration loop where the extra argument /// This is useful for calling predicate closures within an iteration loop where the extra argument
/// is the current element's index. /// is the current element's index.
@ -374,17 +376,19 @@ impl FnPtr {
fn_name: &str, fn_name: &str,
ctx: &NativeCallContext, ctx: &NativeCallContext,
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
items: [Dynamic; N], args: [Dynamic; N],
extras: [Dynamic; E], extras: [Dynamic; E],
move_this_ptr_to_args: Option<usize>,
) -> RhaiResult { ) -> RhaiResult {
self._call_with_extra_args(fn_name, ctx, this_ptr, items, extras) self._call_with_extra_args(fn_name, ctx, this_ptr, args, extras, move_this_ptr_to_args)
} }
/// _(internals)_ Make a call to a function pointer with either a specified number of arguments, /// _(internals)_ Make a call to a function pointer with either a specified number of arguments,
/// or with extra arguments attached. /// or with extra arguments attached. Exported under the `internals` feature only.
/// Exported under the `internals` feature only.
/// ///
/// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`. /// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`.
/// When an appropriate function is not found, it is then removed and mapped to the first parameter. ///
/// When an appropriate function is not found and `move_this_ptr_to_args` is `Some`, `this_ptr`
/// is removed and inserted as the appropriate parameter number.
/// ///
/// This is useful for calling predicate closures within an iteration loop where the extra /// This is useful for calling predicate closures within an iteration loop where the extra
/// argument is the current element's index. /// argument is the current element's index.
@ -398,10 +402,11 @@ impl FnPtr {
fn_name: &str, fn_name: &str,
ctx: &NativeCallContext, ctx: &NativeCallContext,
this_ptr: Option<&mut Dynamic>, this_ptr: Option<&mut Dynamic>,
items: [Dynamic; N], args: [Dynamic; N],
extras: [Dynamic; E], extras: [Dynamic; E],
move_this_ptr_to_args: Option<usize>,
) -> RhaiResult { ) -> RhaiResult {
self._call_with_extra_args(fn_name, ctx, this_ptr, items, extras) self._call_with_extra_args(fn_name, ctx, this_ptr, args, extras, move_this_ptr_to_args)
} }
/// Make a call to a function pointer with either a specified number of arguments, or with extra /// Make a call to a function pointer with either a specified number of arguments, or with extra
/// arguments attached. /// arguments attached.
@ -410,51 +415,94 @@ impl FnPtr {
fn_name: &str, fn_name: &str,
ctx: &NativeCallContext, ctx: &NativeCallContext,
mut this_ptr: Option<&mut Dynamic>, mut this_ptr: Option<&mut Dynamic>,
items: [Dynamic; N], args: [Dynamic; N],
extras: [Dynamic; E], extras: [Dynamic; E],
move_this_ptr_to_args: Option<usize>,
) -> RhaiResult { ) -> RhaiResult {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
if let Some(arity) = self.fn_def().map(|f| f.params.len()) { if let Some(arity) = self.fn_def().map(|f| f.params.len()) {
if arity == N + 1 && this_ptr.is_some() { if arity == N + self.curry().len() {
let mut args = FnArgsVec::with_capacity(items.len() + 1); return self.call_raw(ctx, this_ptr, args);
args.push(this_ptr.as_mut().unwrap().clone());
args.extend(items);
return self.call_raw(ctx, None, args);
} }
if arity == N { if let Some(move_to_args) = move_this_ptr_to_args {
return self.call_raw(ctx, this_ptr, items); if this_ptr.is_some() {
if arity == N + 1 + self.curry().len() {
let mut args2 = FnArgsVec::with_capacity(args.len() + 1);
if move_to_args == 0 {
args2.push(this_ptr.as_mut().unwrap().clone());
args2.extend(args);
} else {
args2.extend(args);
args2.insert(move_to_args, this_ptr.as_mut().unwrap().clone());
} }
if arity == N + E { return self.call_raw(ctx, None, args2);
let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len()); }
items2.extend(IntoIterator::into_iter(items)); if arity == N + E + 1 + self.curry().len() {
items2.extend(IntoIterator::into_iter(extras)); let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1);
return self.call_raw(ctx, this_ptr, items2); if move_to_args == 0 {
args2.push(this_ptr.as_mut().unwrap().clone());
args2.extend(args);
args2.extend(extras);
} else {
args2.extend(args);
args2.insert(move_to_args, this_ptr.as_mut().unwrap().clone());
args2.extend(extras);
}
return self.call_raw(ctx, None, args2);
}
}
}
if arity == N + E + self.curry().len() {
let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len());
args2.extend(args);
args2.extend(extras);
return self.call_raw(ctx, this_ptr, args2);
} }
} }
self.call_raw(ctx, this_ptr.as_deref_mut(), items.clone()) self.call_raw(ctx, this_ptr.as_deref_mut(), args.clone())
.or_else(|err| match *err { .or_else(|err| match *err {
ERR::ErrorFunctionNotFound(sig, ..) ERR::ErrorFunctionNotFound(sig, ..)
if this_ptr.is_some() && sig.starts_with(self.fn_name()) => if move_this_ptr_to_args.is_some()
&& this_ptr.is_some()
&& sig.starts_with(self.fn_name()) =>
{ {
let mut args = FnArgsVec::with_capacity(items.len() + 1); let mut args2 = FnArgsVec::with_capacity(args.len() + 1);
args.push(this_ptr.as_mut().unwrap().clone()); let move_to_args = move_this_ptr_to_args.unwrap();
args.extend(IntoIterator::into_iter(items.clone())); if move_to_args == 0 {
self.call_raw(ctx, this_ptr.as_deref_mut(), args) args2.push(this_ptr.as_mut().unwrap().clone());
args2.extend(args.clone());
} else {
args2.extend(args.clone());
args2.insert(move_to_args, this_ptr.as_mut().unwrap().clone());
}
self.call_raw(ctx, None, args2)
} }
_ => Err(err), _ => Err(err),
}) })
.or_else(|err| match *err { .or_else(|err| match *err {
ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => { ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => {
let mut args = FnArgsVec::with_capacity( if let Some(move_to_args) = move_this_ptr_to_args {
items.len() + extras.len() + if this_ptr.is_some() { 1 } else { 0 },
);
if let Some(ref mut this_ptr) = this_ptr { if let Some(ref mut this_ptr) = this_ptr {
args.push(this_ptr.clone()); let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len() + 1);
if move_to_args == 0 {
args2.push(this_ptr.clone());
args2.extend(args);
args2.extend(extras);
} else {
args2.extend(args);
args2.extend(extras);
args2.insert(move_to_args, this_ptr.clone());
} }
args.extend(IntoIterator::into_iter(items)); return self.call_raw(ctx, None, args2);
args.extend(IntoIterator::into_iter(extras)); }
self.call_raw(ctx, this_ptr, args) }
let mut args2 = FnArgsVec::with_capacity(args.len() + extras.len());
args2.extend(args);
args2.extend(extras);
self.call_raw(ctx, this_ptr, args2)
} }
_ => Err(err), _ => Err(err),
}) })
@ -488,6 +536,13 @@ impl TryFrom<ImmutableString> for FnPtr {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
fn_def: None, fn_def: None,
}) })
} else if is_reserved_keyword_or_symbol(&value)
|| Token::lookup_symbol_from_syntax(&value).is_some()
{
Err(
ERR::ErrorParsing(ParseErrorType::Reserved(value.to_string()), Position::NONE)
.into(),
)
} else { } else {
Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into()) Err(ERR::ErrorFunctionNotFound(value.to_string(), Position::NONE).into())
} }

View File

@ -299,11 +299,31 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("[1].map(|x| x + 41)[0]")?, 42); assert_eq!(engine.eval::<INT>("[1].map(|x| x + 41)[0]")?, 42);
assert_eq!(engine.eval::<INT>("[1].map(|| this + 41)[0]")?, 42); assert_eq!(engine.eval::<INT>("[1].map(|| this + 41)[0]")?, 42);
assert_eq!(
engine.eval::<INT>("let x = [1, 2, 3]; x.for_each(|| this += 41); x[0]")?,
42
);
assert_eq!(
engine.eval::<INT>(
"
let x = [1, 2, 3];
let sum = 0;
let factor = 2;
x.for_each(|| sum += this * factor);
sum
"
)?,
12
);
assert_eq!(engine.eval::<INT>("([1].map(|x| x + 41))[0]")?, 42); assert_eq!(engine.eval::<INT>("([1].map(|x| x + 41))[0]")?, 42);
assert_eq!( assert_eq!(
engine.eval::<INT>("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?, engine.eval::<INT>("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?,
42 42
); );
assert_eq!(
engine.eval::<INT>("let x = [1, 2, 3]; x.for_each(|i| this += i); x[2]")?,
5
);
assert_eq!( assert_eq!(
engine engine
@ -387,18 +407,31 @@ fn test_arrays_map_reduce() -> Result<(), Box<EvalAltResult>> {
14 14
); );
assert_eq!( // assert_eq!(
engine.eval::<INT>( // engine.eval::<INT>(
" // "
let x = [1, 2, 3]; // let x = [1, 2, 3];
x.reduce(|sum, v, i| { // x.reduce(|sum, v, i| {
if i == 0 { sum = 10 } // if i == 0 { sum = 10 }
sum + v * v // sum + v * v
}) // })
" // "
)?, // )?,
24 // 24
); // );
// assert_eq!(
// engine.eval::<INT>(
// "
// let x = [1, 2, 3];
// x.reduce(|sum, i| {
// if i == 0 { sum = 10 }
// sum + this * this
// })
// "
// )?,
// 24
// );
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(

View File

@ -175,7 +175,7 @@ fn test_max_array_size() -> Result<(), Box<EvalAltResult>> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
" "
let x = [1,2,3]; let x = [1,2];
len([x, x, x]) len([x, x, x])
" "
)?, )?,

View File

@ -332,7 +332,8 @@ fn test_string_split() -> Result<(), Box<EvalAltResult>> {
#[test] #[test]
fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> { fn test_string_interpolated() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); // Make sure strings interpolation works even under raw
let engine = Engine::new_raw();
assert_eq!(engine.eval::<String>("`${}`")?, ""); assert_eq!(engine.eval::<String>("`${}`")?, "");