diff --git a/src/ast.rs b/src/ast.rs index 81b77671..99dc3b95 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -1300,7 +1300,7 @@ pub enum Expr { Variable(Box<(Option, Option<(u64, NamespaceRef)>, Ident)>), /// Property access - (getter, hash, setter, hash, prop) Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>), - /// { [statement][Stmt] } + /// { [statement][Stmt] ... } Stmt(Box>, Position), /// func `(` expr `,` ... `)` FnCall(Box, Position), @@ -1308,8 +1308,6 @@ pub enum Expr { Dot(Box, Position), /// expr `[` expr `]` Index(Box, Position), - /// lhs `in` rhs - In(Box, Position), /// lhs `&&` rhs And(Box, Position), /// lhs `||` rhs @@ -1397,7 +1395,7 @@ impl Expr { Self::Variable(x) => (x.2).pos, Self::FnCall(_, pos) => *pos, - Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(), + Self::And(x, _) | Self::Or(x, _) => x.lhs.position(), Self::Unit(pos) => *pos, @@ -1424,7 +1422,7 @@ impl Expr { Self::Property(x) => (x.4).pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos, - Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos, + Self::And(_, pos) | Self::Or(_, pos) => *pos = new_pos, Self::Unit(pos) => *pos = new_pos, Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos, Self::Custom(_, pos) => *pos = new_pos, @@ -1441,7 +1439,7 @@ impl Expr { Self::Map(x, _) => x.iter().map(|(_, v)| v).all(Self::is_pure), - Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => { + Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) => { x.lhs.is_pure() && x.rhs.is_pure() } @@ -1480,13 +1478,6 @@ impl Expr { // An map literal is constant if all items are constant Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_constant), - // Check in expression - Self::In(x, _) => match (&x.lhs, &x.rhs) { - (Self::StringConstant(_, _), Self::StringConstant(_, _)) - | (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true, - _ => false, - }, - _ => false, } } @@ -1507,7 +1498,6 @@ impl Expr { | Self::IntegerConstant(_, _) | Self::CharConstant(_, _) | Self::FnPointer(_, _) - | Self::In(_, _) | Self::And(_, _) | Self::Or(_, _) | Self::Unit(_) => false, @@ -1553,11 +1543,7 @@ impl Expr { Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)), Self::Array(x, _) => x.iter().for_each(|e| e.walk(path, on_node)), Self::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)), - Self::Index(x, _) - | Self::Dot(x, _) - | Expr::In(x, _) - | Expr::And(x, _) - | Expr::Or(x, _) => { + Self::Index(x, _) | Self::Dot(x, _) | Expr::And(x, _) | Expr::Or(x, _) => { x.lhs.walk(path, on_node); x.rhs.walk(path, on_node); } @@ -1582,6 +1568,7 @@ mod tests { assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 4); assert_eq!(size_of::(), 16); + assert_eq!(size_of::(), 32); assert_eq!(size_of::>(), 16); assert_eq!(size_of::(), 32); assert_eq!(size_of::>(), 32); diff --git a/src/engine.rs b/src/engine.rs index 7f169f48..017b2e27 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -202,9 +202,15 @@ pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_SET: &str = "index$set$"; #[cfg(not(feature = "no_function"))] pub const FN_ANONYMOUS: &str = "anon$"; -#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] + +/// Standard equality comparison operator. pub const OP_EQUALS: &str = "=="; +/// Standard method function for containment testing. +/// +/// The `in` operator is implemented as a call to this method. +pub const OP_CONTAINS: &str = "contains"; + /// Method of chaining. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -1609,78 +1615,6 @@ impl Engine { } } - // Evaluate an 'in' expression. - #[inline(always)] - fn eval_in_expr( - &self, - scope: &mut Scope, - mods: &mut Imports, - state: &mut State, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, - lhs: &Expr, - rhs: &Expr, - level: usize, - ) -> RhaiResult { - self.inc_operations(state, rhs.position())?; - - let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?; - - let mut rhs_target = if rhs.get_variable_access(false).is_some() { - let (rhs_ptr, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, rhs)?; - self.inc_operations(state, pos)?; - rhs_ptr - } else { - self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)? - .into() - }; - - match rhs_target.as_mut() { - #[cfg(not(feature = "no_index"))] - Dynamic(Union::Array(rhs_value, _)) => { - // Call the `==` operator to compare each value - let hash = calc_fn_hash(empty(), OP_EQUALS, 2); - for value in rhs_value.iter_mut() { - let args = &mut [&mut lhs_value.clone(), &mut value.clone()]; - let pos = rhs.position(); - - if self - .call_native_fn(mods, state, lib, OP_EQUALS, hash, args, false, false, pos)? - .0 - .as_bool() - .unwrap_or(false) - { - return Ok(true.into()); - } - } - - Ok(false.into()) - } - #[cfg(not(feature = "no_object"))] - Dynamic(Union::Map(rhs_value, _)) => { - // Only allows string or char - if let Ok(c) = lhs_value.as_char() { - Ok(rhs_value.contains_key(&c.to_string()).into()) - } else if let Some(s) = lhs_value.read_lock::() { - Ok(rhs_value.contains_key(&*s).into()) - } else { - EvalAltResult::ErrorInExpr(lhs.position()).into() - } - } - Dynamic(Union::Str(rhs_value, _)) => { - // Only allows string or char - if let Ok(c) = lhs_value.as_char() { - Ok(rhs_value.contains(c).into()) - } else if let Some(s) = lhs_value.read_lock::() { - Ok(rhs_value.contains(s.as_str()).into()) - } else { - EvalAltResult::ErrorInExpr(lhs.position()).into() - } - } - _ => EvalAltResult::ErrorInExpr(rhs.position()).into(), - } - } - /// Evaluate an expression. pub(crate) fn eval_expr( &self, @@ -1792,10 +1726,6 @@ impl Engine { ) } - Expr::In(x, _) => { - self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.lhs, &x.rhs, level) - } - Expr::And(x, _) => { Ok((self .eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)? diff --git a/src/fn_builtin.rs b/src/fn_builtin.rs index 8a1d96d8..c592bb0e 100644 --- a/src/fn_builtin.rs +++ b/src/fn_builtin.rs @@ -1,5 +1,6 @@ //! Built-in implementations for common operators. +use crate::engine::OP_CONTAINS; use crate::fn_native::{FnCallArgs, NativeCallContext}; use crate::stdlib::{any::TypeId, format, string::ToString}; use crate::{Dynamic, ImmutableString, RhaiResult, INT}; @@ -77,6 +78,13 @@ pub fn get_builtin_binary_op_fn( Ok((x $op y).into()) }) }; + ($xx:ident . $func:ident ( $yy:ty )) => { + return Some(|_, args| { + let x = &*args[0].read_lock::<$xx>().unwrap(); + let y = &*args[1].read_lock::<$yy>().unwrap(); + Ok(x.$func(y).into()) + }) + }; ($func:ident ( $op:tt )) => { return Some(|_, args| { let (x, y) = $func(args); @@ -259,6 +267,24 @@ pub fn get_builtin_binary_op_fn( ">=" => impl_op!(get_s1s2(>=)), "<" => impl_op!(get_s1s2(<)), "<=" => impl_op!(get_s1s2(<=)), + OP_CONTAINS => { + return Some(|_, args| { + let s = &*args[0].read_lock::().unwrap(); + let c = args[1].as_char().unwrap(); + Ok((s.contains(c)).into()) + }) + } + _ => return None, + } + } + + // map op string + #[cfg(not(feature = "no_object"))] + if types_pair == (TypeId::of::(), TypeId::of::()) { + use crate::Map; + + match op { + OP_CONTAINS => impl_op!(Map.contains_key(ImmutableString)), _ => return None, } } @@ -342,6 +368,13 @@ pub fn get_builtin_binary_op_fn( ">=" => impl_op!(ImmutableString >= ImmutableString), "<" => impl_op!(ImmutableString < ImmutableString), "<=" => impl_op!(ImmutableString <= ImmutableString), + OP_CONTAINS => { + return Some(|_, args| { + let s1 = &*args[0].read_lock::().unwrap(); + let s2 = &*args[1].read_lock::().unwrap(); + Ok((s1.contains(s2.as_str())).into()) + }) + } _ => return None, } } diff --git a/src/fn_call.rs b/src/fn_call.rs index d4fe037f..c3ed8d30 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -831,6 +831,7 @@ impl Engine { } /// Evaluate a text script in place - used primarily for 'eval'. + #[inline] fn eval_script_expr_in_place( &self, scope: &mut Scope, diff --git a/src/lib.rs b/src/lib.rs index aa95de62..d36ec5b9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -124,7 +124,7 @@ pub type FLOAT = f32; pub use ast::{FnAccess, AST}; pub use dynamic::Dynamic; -pub use engine::{Engine, EvalContext}; +pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS}; pub use fn_native::{FnPtr, NativeCallContext}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::{FnNamespace, Module}; diff --git a/src/optimize.rs b/src/optimize.rs index 20456ad2..25f423ee 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -15,7 +15,6 @@ use crate::stdlib::{ vec, vec::Vec, }; -use crate::token::is_valid_identifier; use crate::utils::get_hasher; use crate::{ calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope, @@ -598,32 +597,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // #{ key:value, .. } #[cfg(not(feature = "no_object"))] Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)), - // lhs in rhs - Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) { - // "xxx" in "xxxxx" - (Expr::StringConstant(a, pos), Expr::StringConstant(b, _)) => { - state.set_dirty(); - *expr = Expr::BoolConstant( b.contains(a.as_str()), *pos); - } - // 'x' in "xxxxx" - (Expr::CharConstant(a, pos), Expr::StringConstant(b, _)) => { - state.set_dirty(); - *expr = Expr::BoolConstant(b.contains(*a), *pos); - } - // "xxx" in #{...} - (Expr::StringConstant(a, pos), Expr::Map(b, _)) => { - state.set_dirty(); - *expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == *a).is_some(), *pos); - } - // 'x' in #{...} - (Expr::CharConstant(a, pos), Expr::Map(b, _)) => { - state.set_dirty(); - let ch = a.to_string(); - *expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == &ch).is_some(), *pos); - } - // lhs in rhs - (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } - }, // lhs && rhs Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) { // true && rhs -> rhs @@ -684,7 +657,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { && state.optimization_level == OptimizationLevel::Simple // simple optimizations && x.args.len() == 2 // binary call && x.args.iter().all(Expr::is_constant) // all arguments are constants - && !is_valid_identifier(x.name.chars()) // cannot be scripted + //&& !is_valid_identifier(x.name.chars()) // cannot be scripted => { let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect(); diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 8b39e09f..7e3012f9 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -237,11 +237,11 @@ mod array_functions { pub fn contains( ctx: NativeCallContext, array: &mut Array, - mut value: Dynamic, + value: Dynamic, ) -> Result> { - for item in array.iter() { + for item in array.iter_mut() { if ctx - .call_fn_dynamic_raw(OP_EQUALS, true, &mut [&mut value, &mut item.clone()]) + .call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => @@ -268,11 +268,11 @@ mod array_functions { pub fn index_of( ctx: NativeCallContext, array: &mut Array, - mut value: Dynamic, + value: Dynamic, ) -> Result> { - for (i, item) in array.iter().enumerate() { + for (i, item) in array.iter_mut().enumerate() { if ctx - .call_fn_dynamic_raw(OP_EQUALS, true, &mut [&mut value, &mut item.clone()]) + .call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()]) .or_else(|err| match *err { EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) if fn_sig.starts_with(OP_EQUALS) => diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index e1c0558b..f54af86c 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -13,8 +13,8 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { #[export_module] mod map_functions { - #[rhai_fn(pure)] - pub fn has(map: &mut Map, prop: ImmutableString) -> bool { + #[rhai_fn(name = "has", pure)] + pub fn contains(map: &mut Map, prop: ImmutableString) -> bool { map.contains_key(&prop) } #[rhai_fn(pure)] diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 783dc2c4..ce3d819e 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -74,14 +74,6 @@ mod string_functions { } } - #[rhai_fn(name = "contains")] - pub fn contains_char(string: &str, character: char) -> bool { - string.contains(character) - } - pub fn contains(string: &str, find_string: &str) -> bool { - string.contains(find_string) - } - #[rhai_fn(name = "index_of")] pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { let start = if start < 0 { diff --git a/src/parser.rs b/src/parser.rs index 3361921b..3b1d05f7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -5,7 +5,7 @@ use crate::ast::{ Stmt, }; use crate::dynamic::{AccessMode, Union}; -use crate::engine::KEYWORD_THIS; +use crate::engine::{KEYWORD_THIS, OP_CONTAINS}; use crate::module::NamespaceRef; use crate::optimize::optimize_into_ast; use crate::optimize::OptimizationLevel; @@ -480,7 +480,6 @@ fn parse_index_chain( Expr::CharConstant(_, _) | Expr::And(_, _) | Expr::Or(_, _) - | Expr::In(_, _) | Expr::BoolConstant(_, _) | Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( @@ -514,7 +513,6 @@ fn parse_index_chain( Expr::CharConstant(_, _) | Expr::And(_, _) | Expr::Or(_, _) - | Expr::In(_, _) | Expr::BoolConstant(_, _) | Expr::Unit(_) => { return Err(PERR::MalformedIndexExpr( @@ -548,8 +546,8 @@ fn parse_index_chain( ) .into_err(x.position())) } - // lhs[??? && ???], lhs[??? || ???], lhs[??? in ???] - x @ Expr::And(_, _) | x @ Expr::Or(_, _) | x @ Expr::In(_, _) => { + // lhs[??? && ???], lhs[??? || ???] + x @ Expr::And(_, _) | x @ Expr::Or(_, _) => { return Err(PERR::MalformedIndexExpr( "Array access expects integer index, not a boolean".into(), ) @@ -1602,139 +1600,6 @@ fn make_dot_expr( }) } -/// Make an 'in' expression. -fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { - match (&lhs, &rhs) { - (_, x @ Expr::IntegerConstant(_, _)) - | (_, x @ Expr::And(_, _)) - | (_, x @ Expr::Or(_, _)) - | (_, x @ Expr::In(_, _)) - | (_, x @ Expr::BoolConstant(_, _)) - | (_, x @ Expr::Unit(_)) => { - return Err(PERR::MalformedInExpr( - "'in' expression expects a string, array or object map".into(), - ) - .into_err(x.position())) - } - - #[cfg(not(feature = "no_float"))] - (_, x @ Expr::FloatConstant(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression expects a string, array or object map".into(), - ) - .into_err(x.position())) - } - - // "xxx" in "xxxx", 'x' in "xxxx" - OK! - (Expr::StringConstant(_, _), Expr::StringConstant(_, _)) - | (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (), - - // 123.456 in "xxxx" - #[cfg(not(feature = "no_float"))] - (x @ Expr::FloatConstant(_, _), Expr::StringConstant(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not a float".into(), - ) - .into_err(x.position())) - } - // 123 in "xxxx" - (x @ Expr::IntegerConstant(_, _), Expr::StringConstant(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not a number".into(), - ) - .into_err(x.position())) - } - // (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx", - // true in "xxxx", false in "xxxx" - (x @ Expr::And(_, _), Expr::StringConstant(_, _)) - | (x @ Expr::Or(_, _), Expr::StringConstant(_, _)) - | (x @ Expr::In(_, _), Expr::StringConstant(_, _)) - | (x @ Expr::BoolConstant(_, _), Expr::StringConstant(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not a boolean".into(), - ) - .into_err(x.position())) - } - // [???, ???, ???] in "xxxx" - (x @ Expr::Array(_, _), Expr::StringConstant(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not an array".into(), - ) - .into_err(x.position())) - } - // #{...} in "xxxx" - (x @ Expr::Map(_, _), Expr::StringConstant(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not an object map".into(), - ) - .into_err(x.position())) - } - // () in "xxxx" - (x @ Expr::Unit(_), Expr::StringConstant(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for a string expects a string, not ()".into(), - ) - .into_err(x.position())) - } - - // "xxx" in #{...}, 'x' in #{...} - OK! - (Expr::StringConstant(_, _), Expr::Map(_, _)) - | (Expr::CharConstant(_, _), Expr::Map(_, _)) => (), - - // 123.456 in #{...} - #[cfg(not(feature = "no_float"))] - (x @ Expr::FloatConstant(_, _), Expr::Map(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not a float".into(), - ) - .into_err(x.position())) - } - // 123 in #{...} - (x @ Expr::IntegerConstant(_, _), Expr::Map(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not a number".into(), - ) - .into_err(x.position())) - } - // (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...}, - // true in #{...}, false in #{...} - (x @ Expr::And(_, _), Expr::Map(_, _)) - | (x @ Expr::Or(_, _), Expr::Map(_, _)) - | (x @ Expr::In(_, _), Expr::Map(_, _)) - | (x @ Expr::BoolConstant(_, _), Expr::Map(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not a boolean".into(), - ) - .into_err(x.position())) - } - // [???, ???, ???] in #{..} - (x @ Expr::Array(_, _), Expr::Map(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not an array".into(), - ) - .into_err(x.position())) - } - // #{...} in #{..} - (x @ Expr::Map(_, _), Expr::Map(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not an object map".into(), - ) - .into_err(x.position())) - } - // () in #{...} - (x @ Expr::Unit(_), Expr::Map(_, _)) => { - return Err(PERR::MalformedInExpr( - "'in' expression for an object map expects a string, not ()".into(), - ) - .into_err(x.position())) - } - - _ => (), - } - - Ok(Expr::In(Box::new(BinaryExpr { lhs, rhs }), op_pos)) -} - /// Parse a binary expression. fn parse_binary_op( input: &mut TokenStream, @@ -1880,9 +1745,21 @@ fn parse_binary_op( ) } Token::In => { - let rhs = args.pop().unwrap(); - let current_lhs = args.pop().unwrap(); - make_in_expr(current_lhs, rhs, pos)? + // Swap the arguments + let current_lhs = args.remove(0); + args.push(current_lhs); + + // Convert into a call to `contains` + let hash = calc_fn_hash(empty(), OP_CONTAINS, 2); + Expr::FnCall( + Box::new(FnCallExpr { + hash: FnHash::from_script(hash), + args, + name: OP_CONTAINS.into(), + ..op_base + }), + pos, + ) } Token::Custom(s) diff --git a/tests/maps.rs b/tests/maps.rs index 988c51a3..5892c29d 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -38,7 +38,7 @@ fn test_map_indexing() -> Result<(), Box> { engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?; assert!(engine.eval::(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?); - assert!(engine.eval::("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?); + assert!(engine.eval::(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?); assert!(!engine.eval::(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?); assert_eq!(