diff --git a/README.md b/README.md index a641e333..aa478554 100644 --- a/README.md +++ b/README.md @@ -273,10 +273,11 @@ type_of('c') == "char"; type_of(42) == "i64"; let x = 123; -x.type_of() == "i64"; +x.type_of(); // error - 'type_of' cannot use postfix notation +type_of(x) == "i64"; x = 99.999; -x.type_of() == "f64"; +type_of(x) == "f64"; x = "hello"; if type_of(x) == "string" { @@ -538,12 +539,12 @@ with a special "pretty-print" name, [`type_of`] will return that name instead. engine.register_type::(); engine.register_fn("new_ts", TestStruct::new); let x = new_ts(); -print(x.type_of()); // prints "path::to::module::TestStruct" +print(type_of(x)); // prints "path::to::module::TestStruct" engine.register_type_with_name::("Hello"); engine.register_fn("new_ts", TestStruct::new); let x = new_ts(); -print(x.type_of()); // prints "Hello" +print(type_of(x)); // prints "Hello" ``` Getters and setters @@ -1527,16 +1528,35 @@ let script = "let y = x;"; // build a script script += "y += foo(y);"; script += "x + y"; -let result = eval(script); +let result = eval(script); // <- look, JS, we can also do this! print("Answer: " + result); // prints 42 -print("x = " + x); // prints 10 (functions call arguments are passed by value) -print("y = " + y); // prints 32 (variables defined in 'eval' persist) +print("x = " + x); // prints 10 - functions call arguments are passed by value +print("y = " + y); // prints 32 - variables defined in 'eval' persist! eval("{ let z = y }"); // to keep a variable local, use a statement block print("z = " + z); // error - variable 'z' not found + +"print(42)".eval(); // nope - just like 'type_of' postfix notation doesn't work +``` + +Script segments passed to `eval` execute inside the current [`Scope`], so they can access and modify _everything_, +including all variables that are visible at that position in code! It is almost as if the script segments were +physically pasted in at the position of the `eval` call. + +```rust +let script = "x += 32"; +let x = 10; +eval(script); // variable 'x' in the current scope is visible! +print(x); // prints 42 + +// The above is equivalent to: +let script = "x += 32"; +let x = 10; +x += 32; +print(x); ``` For those who subscribe to the (very sensible) motto of ["`eval` is **evil**"](http://linterrors.com/js/eval-is-evil), @@ -1547,3 +1567,13 @@ fn eval(script) { throw "eval is evil! I refuse to run " + script } let x = eval("40 + 2"); // 'eval' here throws "eval is evil! I refuse to run 40 + 2" ``` + +Or override it from Rust: + +```rust +fn alt_eval(script: String) -> Result<(), EvalAltResult> { + Err(format!("eval is evil! I refuse to run {}", script).into()) +} + +engine.register_result_fn("eval", alt_eval); +``` diff --git a/src/api.rs b/src/api.rs index d6f592ba..850ff357 100644 --- a/src/api.rs +++ b/src/api.rs @@ -765,10 +765,9 @@ impl<'e> Engine<'e> { name: &str, mut values: Vec, ) -> Result { - let mut scope = Scope::new(); let values: Vec<_> = values.iter_mut().map(Dynamic::as_mut).collect(); - let result = engine.call_fn_raw(&mut scope, name, values, None, Position::none()); + let result = engine.call_fn_raw(name, values, None, Position::none()); result } diff --git a/src/engine.rs b/src/engine.rs index bd389312..d0c81ba4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -3,7 +3,7 @@ use crate::any::{Any, AnyExt, Dynamic, Variant}; use crate::parser::{Expr, FnDef, Position, ReturnType, Stmt}; use crate::result::EvalAltResult; -use crate::scope::{Scope, VariableType}; +use crate::scope::{Scope, ScopeSource, VariableType}; #[cfg(not(feature = "no_optimize"))] use crate::optimize::OptimizationLevel; @@ -161,7 +161,6 @@ impl Engine<'_> { /// registered with the `Engine` or written in Rhai pub(crate) fn call_fn_raw( &mut self, - scope: &mut Scope, fn_name: &str, args: FnCallArgs, def_val: Option<&Dynamic>, @@ -225,40 +224,6 @@ impl Engine<'_> { return Ok(result); } - if fn_name == KEYWORD_TYPE_OF && args.len() == 1 { - // Handle `type_of` function - return Ok(self - .map_type_name(args[0].type_name()) - .to_string() - .into_dynamic()); - } - - if fn_name == KEYWORD_EVAL && args.len() == 1 { - // Handle `eval` function - let script = cast_to_string(args[0], pos)?; - - #[cfg(not(feature = "no_optimize"))] - let ast = { - let orig_optimization_level = self.optimization_level; - - self.set_optimization_level(OptimizationLevel::None); - let ast = self.compile(script); - self.set_optimization_level(orig_optimization_level); - - ast.map_err(EvalAltResult::ErrorParsing)? - }; - - #[cfg(feature = "no_optimize")] - let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; - - return Ok(self - .eval_ast_with_scope_raw(scope, true, &ast) - .map_err(|mut err| { - err.set_position(pos); - err - })?); - } - if fn_name.starts_with(FUNC_GETTER) { // Getter function not found return Err(EvalAltResult::ErrorDotExpr( @@ -299,13 +264,38 @@ impl Engine<'_> { )) } - /// Chain-evaluate a dot setter + /// Chain-evaluate a dot setter. + /// + /// Either `src` or `target` should be `Some`. + /// + /// If `target` is `Some`, then it is taken as the reference to use for `this`. + /// + /// Otherwise, if `src` is `Some`, then it holds a name and index into `scope`; using `get_mut` on + /// `scope` can retrieve a mutable reference to the variable's value to use as `this`. fn get_dot_val_helper( &mut self, scope: &mut Scope, - this_ptr: &mut Variant, + src: Option, + target: Option<&mut Variant>, dot_rhs: &Expr, ) -> Result { + // Get the `this` reference. Either `src` or `target` should be `Some`. + fn get_this_ptr<'a>( + scope: &'a mut Scope, + src: Option, + target: Option<&'a mut Variant>, + ) -> &'a mut Variant { + if let Some(t) = target { + // If `target` is `Some`, then it is returned. + t + } else { + // Otherwise, if `src` is `Some`, then it holds a name and index into `scope`; + // using `get_mut` on `scope` to retrieve a mutable reference for return. + let ScopeSource { name, idx, .. } = src.expect("expected source in scope"); + scope.get_mut(name, idx).as_mut() + } + } + match dot_rhs { // xxx.fn_name(args) Expr::FunctionCall(fn_name, arg_expr_list, def_val, pos) => { @@ -314,36 +304,40 @@ impl Engine<'_> { .map(|arg_expr| self.eval_expr(scope, arg_expr)) .collect::, _>>()?; + let this_ptr = get_this_ptr(scope, src, target); + let args = once(this_ptr) .chain(values.iter_mut().map(Dynamic::as_mut)) .collect(); - self.call_fn_raw(scope, fn_name, args, def_val.as_ref(), *pos) + self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos) } // xxx.id Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - - self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos) + let this_ptr = get_this_ptr(scope, src, target); + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) } // xxx.idx_lhs[idx_expr] #[cfg(not(feature = "no_index"))] - Expr::Index(idx_lhs, idx_expr, idx_pos) => { + Expr::Index(idx_lhs, idx_expr, op_pos) => { let (val, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); + let this_ptr = get_this_ptr(scope, src, target); ( - self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)?, + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, *pos, ) } // xxx.???[???][idx_expr] - Expr::Index(_, _, _) => { - (self.get_dot_val_helper(scope, this_ptr, idx_lhs)?, *idx_pos) - } + Expr::Index(_, _, _) => ( + self.get_dot_val_helper(scope, src, target, idx_lhs)?, + *op_pos, + ), // Syntax error _ => { return Err(EvalAltResult::ErrorDotExpr( @@ -354,7 +348,7 @@ impl Engine<'_> { }; let idx = self.eval_index_value(scope, idx_expr)?; - self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos) .map(|(v, _)| v) } @@ -363,26 +357,31 @@ impl Engine<'_> { // xxx.id.rhs Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); + let this_ptr = get_this_ptr(scope, src, target); - self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos) - .and_then(|mut v| self.get_dot_val_helper(scope, v.as_mut(), rhs)) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + .and_then(|mut v| { + self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs) + }) } // xxx.idx_lhs[idx_expr].rhs #[cfg(not(feature = "no_index"))] - Expr::Index(idx_lhs, idx_expr, idx_pos) => { + Expr::Index(idx_lhs, idx_expr, op_pos) => { let (val, _) = match idx_lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); + let this_ptr = get_this_ptr(scope, src, target); ( - self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)?, + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, *pos, ) } // xxx.???[???][idx_expr].rhs - Expr::Index(_, _, _) => { - (self.get_dot_val_helper(scope, this_ptr, idx_lhs)?, *idx_pos) - } + Expr::Index(_, _, _) => ( + self.get_dot_val_helper(scope, src, target, idx_lhs)?, + *op_pos, + ), // Syntax error _ => { return Err(EvalAltResult::ErrorDotExpr( @@ -393,8 +392,10 @@ impl Engine<'_> { }; let idx = self.eval_index_value(scope, idx_expr)?; - self.get_indexed_value(&val, idx, idx_expr.position(), *idx_pos) - .and_then(|(mut v, _)| self.get_dot_val_helper(scope, v.as_mut(), rhs)) + self.get_indexed_value(&val, idx, idx_expr.position(), *op_pos) + .and_then(|(mut v, _)| { + self.get_dot_val_helper(scope, None, Some(v.as_mut()), rhs) + }) } // Syntax error _ => Err(EvalAltResult::ErrorDotExpr( @@ -421,28 +422,33 @@ impl Engine<'_> { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (src_idx, _, mut target) = Self::search_scope(scope, id, Ok, *pos)?; - let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); + let (ScopeSource { idx, var_type, .. }, _) = + Self::search_scope(scope, id, Ok, *pos)?; - // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - *scope.get_mut(id, src_idx) = target; + // This is a variable property access (potential function call). + // Use a direct index into `scope` to directly mutate the variable value. + let src = ScopeSource { + name: id, + idx, + var_type, + }; - val + self.get_dot_val_helper(scope, Some(src), None, dot_rhs) } // idx_lhs[idx_expr].??? #[cfg(not(feature = "no_index"))] - Expr::Index(idx_lhs, idx_expr, idx_pos) => { + Expr::Index(idx_lhs, idx_expr, op_pos) => { let (src_type, src, idx, mut target) = - self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?; - let val = self.get_dot_val_helper(scope, target.as_mut(), dot_rhs); + self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?; + let val = self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - if let Some((id, var_type, src_idx)) = src { - match var_type { + if let Some(src) = src { + match src.var_type { VariableType::Constant => { return Err(EvalAltResult::ErrorAssignmentToConstant( - id.to_string(), + src.name.to_string(), idx_lhs.position(), )); } @@ -450,11 +456,9 @@ impl Engine<'_> { Self::update_indexed_var_in_scope( src_type, scope, - id, - src_idx, + src, idx, - target, - dot_rhs.position(), + (target, dot_rhs.position()), )?; } } @@ -466,22 +470,22 @@ impl Engine<'_> { // {expr}.??? expr => { let mut target = self.eval_expr(scope, expr)?; - self.get_dot_val_helper(scope, target.as_mut(), dot_rhs) + self.get_dot_val_helper(scope, None, Some(target.as_mut()), dot_rhs) } } } /// Search for a variable within the scope, returning its value and index inside the Scope - fn search_scope( - scope: &Scope, + fn search_scope<'a, T>( + scope: &'a Scope, id: &str, - map: impl FnOnce(Dynamic) -> Result, + convert: impl FnOnce(Dynamic) -> Result, begin: Position, - ) -> Result<(usize, VariableType, T), EvalAltResult> { + ) -> Result<(ScopeSource<'a>, T), EvalAltResult> { scope .get(id) .ok_or_else(|| EvalAltResult::ErrorVariableNotFound(id.into(), begin)) - .and_then(move |(idx, _, var_type, val)| map(val).map(|v| (idx, var_type, v))) + .and_then(move |(src, value)| convert(value).map(|v| (src, v))) } /// Evaluate the value of an index (must evaluate to INT) @@ -503,8 +507,8 @@ impl Engine<'_> { &self, val: &Dynamic, idx: INT, - val_pos: Position, idx_pos: Position, + op_pos: Position, ) -> Result<(Dynamic, IndexSourceType), EvalAltResult> { if val.is::() { // val_array[idx] @@ -514,9 +518,9 @@ impl Engine<'_> { arr.get(idx as usize) .cloned() .map(|v| (v, IndexSourceType::Array)) - .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, val_pos)) + .ok_or_else(|| EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) } else { - Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, val_pos)) + Err(EvalAltResult::ErrorArrayBounds(arr.len(), idx, idx_pos)) } } else if val.is::() { // val_string[idx] @@ -527,20 +531,20 @@ impl Engine<'_> { .nth(idx as usize) .map(|ch| (ch.into_dynamic(), IndexSourceType::String)) .ok_or_else(|| { - EvalAltResult::ErrorStringBounds(s.chars().count(), idx, val_pos) + EvalAltResult::ErrorStringBounds(s.chars().count(), idx, idx_pos) }) } else { Err(EvalAltResult::ErrorStringBounds( s.chars().count(), idx, - val_pos, + idx_pos, )) } } else { // Error - cannot be indexed Err(EvalAltResult::ErrorIndexingType( self.map_type_name(val.type_name()).to_string(), - idx_pos, + op_pos, )) } } @@ -552,16 +556,8 @@ impl Engine<'_> { scope: &mut Scope, lhs: &'a Expr, idx_expr: &Expr, - idx_pos: Position, - ) -> Result< - ( - IndexSourceType, - Option<(&'a str, VariableType, usize)>, - usize, - Dynamic, - ), - EvalAltResult, - > { + op_pos: Position, + ) -> Result<(IndexSourceType, Option>, usize, Dynamic), EvalAltResult> { let idx = self.eval_index_value(scope, idx_expr)?; match lhs { @@ -569,13 +565,17 @@ impl Engine<'_> { Expr::Variable(id, _) => Self::search_scope( scope, &id, - |val| self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos), + |val| self.get_indexed_value(&val, idx, idx_expr.position(), op_pos), lhs.position(), ) - .map(|(src_idx, var_type, (val, src_type))| { + .map(|(src, (val, src_type))| { ( src_type, - Some((id.as_str(), var_type, src_idx)), + Some(ScopeSource { + name: &id, + var_type: src.var_type, + idx: src.idx, + }), idx as usize, val, ) @@ -585,7 +585,7 @@ impl Engine<'_> { expr => { let val = self.eval_expr(scope, expr)?; - self.get_indexed_value(&val, idx, idx_expr.position(), idx_pos) + self.get_indexed_value(&val, idx, idx_expr.position(), op_pos) .map(|(v, _)| (IndexSourceType::Expression, None, idx as usize, v)) } } @@ -610,26 +610,26 @@ impl Engine<'_> { fn update_indexed_var_in_scope( src_type: IndexSourceType, scope: &mut Scope, - id: &str, - src_idx: usize, + src: ScopeSource, idx: usize, - new_val: Dynamic, - val_pos: Position, + new_val: (Dynamic, Position), ) -> Result { match src_type { // array_id[idx] = val IndexSourceType::Array => { - let arr = scope.get_mut_by_type::(id, src_idx); - Ok((arr[idx as usize] = new_val).into_dynamic()) + let arr = scope.get_mut_by_type::(src.name, src.idx); + Ok((arr[idx as usize] = new_val.0).into_dynamic()) } // string_id[idx] = val IndexSourceType::String => { - let s = scope.get_mut_by_type::(id, src_idx); + let s = scope.get_mut_by_type::(src.name, src.idx); + let pos = new_val.1; // Value must be a character let ch = *new_val + .0 .downcast::() - .map_err(|_| EvalAltResult::ErrorCharMismatch(val_pos))?; + .map_err(|_| EvalAltResult::ErrorCharMismatch(pos))?; Ok(Self::str_replace_char(s, idx as usize, ch).into_dynamic()) } @@ -669,52 +669,44 @@ impl Engine<'_> { scope: &mut Scope, this_ptr: &mut Variant, dot_rhs: &Expr, - mut new_val: Dynamic, - val_pos: Position, + new_val: (&mut Dynamic, Position), ) -> Result { match dot_rhs { // xxx.id Expr::Property(id, pos) => { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - self.call_fn_raw( - scope, - &set_fn_name, - vec![this_ptr, new_val.as_mut()], - None, - *pos, - ) + self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.0.as_mut()], None, *pos) } // xxx.lhs[idx_expr] // TODO - Allow chaining of indexing! #[cfg(not(feature = "no_index"))] - Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { + Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr] Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|v| { let idx = self.eval_index_value(scope, idx_expr)?; - Self::update_indexed_value(v, idx as usize, new_val, val_pos) + Self::update_indexed_value( + v, + idx as usize, + new_val.0.clone(), + new_val.1, + ) }) .and_then(|mut v| { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - self.call_fn_raw( - scope, - &set_fn_name, - vec![this_ptr, v.as_mut()], - None, - *pos, - ) + self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) }) } // All others - syntax error for setters chain _ => Err(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), - *idx_pos, + *op_pos, )), }, @@ -724,45 +716,34 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|mut v| { - self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val, val_pos) + self.set_dot_val_helper(scope, v.as_mut(), rhs, new_val) .map(|_| v) // Discard Ok return value }) .and_then(|mut v| { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - self.call_fn_raw( - scope, - &set_fn_name, - vec![this_ptr, v.as_mut()], - None, - *pos, - ) + self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) }) } // xxx.lhs[idx_expr].rhs // TODO - Allow chaining of indexing! #[cfg(not(feature = "no_index"))] - Expr::Index(lhs, idx_expr, idx_pos) => match lhs.as_ref() { + Expr::Index(lhs, idx_expr, op_pos) => match lhs.as_ref() { // xxx.id[idx_expr].rhs Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) .and_then(|v| { let idx = self.eval_index_value(scope, idx_expr)?; let (mut target, _) = - self.get_indexed_value(&v, idx, idx_expr.position(), *idx_pos)?; + self.get_indexed_value(&v, idx, idx_expr.position(), *op_pos)?; - self.set_dot_val_helper( - scope, - target.as_mut(), - rhs, - new_val, - val_pos, - )?; + let val_pos = new_val.1; + self.set_dot_val_helper(scope, target.as_mut(), rhs, new_val)?; // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. Self::update_indexed_value(v, idx as usize, target, val_pos) @@ -771,7 +752,6 @@ impl Engine<'_> { let set_fn_name = format!("{}{}", FUNC_SETTER, id); self.call_fn_raw( - scope, &set_fn_name, vec![this_ptr, v.as_mut()], None, @@ -783,7 +763,7 @@ impl Engine<'_> { // All others - syntax error for setters chain _ => Err(EvalAltResult::ErrorDotExpr( "for assignment".to_string(), - *idx_pos, + *op_pos, )), }, @@ -808,14 +788,14 @@ impl Engine<'_> { scope: &mut Scope, dot_lhs: &Expr, dot_rhs: &Expr, - new_val: Dynamic, - val_pos: Position, + new_val: (&mut Dynamic, Position), op_pos: Position, ) -> Result { match dot_lhs { // id.??? Expr::Variable(id, pos) => { - let (src_idx, var_type, mut target) = Self::search_scope(scope, id, Ok, *pos)?; + let (ScopeSource { idx, var_type, .. }, mut target) = + Self::search_scope(scope, id, Ok, *pos)?; match var_type { VariableType::Constant => { @@ -827,11 +807,10 @@ impl Engine<'_> { _ => (), } - let val = - self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos); + let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - *scope.get_mut(id, src_idx) = target; + *scope.get_mut(id, idx) = target; val } @@ -839,24 +818,28 @@ impl Engine<'_> { // lhs[idx_expr].??? // TODO - Allow chaining of indexing! #[cfg(not(feature = "no_index"))] - Expr::Index(lhs, idx_expr, idx_pos) => { + Expr::Index(lhs, idx_expr, op_pos) => { let (src_type, src, idx, mut target) = - self.eval_index_expr(scope, lhs, idx_expr, *idx_pos)?; - let val = - self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val, val_pos); + self.eval_index_expr(scope, lhs, idx_expr, *op_pos)?; + let val_pos = new_val.1; + let val = self.set_dot_val_helper(scope, target.as_mut(), dot_rhs, new_val); // In case the expression mutated `target`, we need to update it back into the scope because it is cloned. - if let Some((id, var_type, src_idx)) = src { - match var_type { + if let Some(src) = src { + match src.var_type { VariableType::Constant => { return Err(EvalAltResult::ErrorAssignmentToConstant( - id.to_string(), + src.name.to_string(), lhs.position(), )); } VariableType::Normal => { Self::update_indexed_var_in_scope( - src_type, scope, id, src_idx, idx, target, val_pos, + src_type, + scope, + src, + idx, + (target, val_pos), )?; } } @@ -882,15 +865,13 @@ impl Engine<'_> { Expr::IntegerConstant(i, _) => Ok(i.into_dynamic()), Expr::StringConstant(s, _) => Ok(s.into_dynamic()), Expr::CharConstant(c, _) => Ok(c.into_dynamic()), - Expr::Variable(id, pos) => { - Self::search_scope(scope, id, Ok, *pos).map(|(_, _, val)| val) - } + Expr::Variable(id, pos) => Self::search_scope(scope, id, Ok, *pos).map(|(_, val)| val), Expr::Property(_, _) => panic!("unexpected property."), // lhs[idx_expr] #[cfg(not(feature = "no_index"))] - Expr::Index(lhs, idx_expr, idx_pos) => self - .eval_index_expr(scope, lhs, idx_expr, *idx_pos) + Expr::Index(lhs, idx_expr, op_pos) => self + .eval_index_expr(scope, lhs, idx_expr, *op_pos) .map(|(_, _, _, x)| x), // Statement block @@ -898,43 +879,55 @@ impl Engine<'_> { // lhs = rhs Expr::Assignment(lhs, rhs, op_pos) => { - let rhs_val = self.eval_expr(scope, rhs)?; + let mut rhs_val = self.eval_expr(scope, rhs)?; match lhs.as_ref() { // name = rhs Expr::Variable(name, pos) => match scope.get(name) { - Some((idx, _, VariableType::Normal, _)) => { + Some(( + ScopeSource { + idx, + var_type: VariableType::Normal, + .. + }, + _, + )) => { *scope.get_mut(name, idx) = rhs_val.clone(); Ok(rhs_val) } - Some((_, _, VariableType::Constant, _)) => Err( - EvalAltResult::ErrorAssignmentToConstant(name.to_string(), *op_pos), - ), + Some(( + ScopeSource { + var_type: VariableType::Constant, + .. + }, + _, + )) => Err(EvalAltResult::ErrorAssignmentToConstant( + name.to_string(), + *op_pos, + )), _ => Err(EvalAltResult::ErrorVariableNotFound(name.clone(), *pos)), }, // idx_lhs[idx_expr] = rhs #[cfg(not(feature = "no_index"))] - Expr::Index(idx_lhs, idx_expr, idx_pos) => { + Expr::Index(idx_lhs, idx_expr, op_pos) => { let (src_type, src, idx, _) = - self.eval_index_expr(scope, idx_lhs, idx_expr, *idx_pos)?; + self.eval_index_expr(scope, idx_lhs, idx_expr, *op_pos)?; - if let Some((id, var_type, src_idx)) = src { - match var_type { + if let Some(src) = src { + match src.var_type { VariableType::Constant => { return Err(EvalAltResult::ErrorAssignmentToConstant( - id.to_string(), + src.name.to_string(), idx_lhs.position(), )); } VariableType::Normal => Ok(Self::update_indexed_var_in_scope( src_type, scope, - &id, - src_idx, + src, idx, - rhs_val, - rhs.position(), + (rhs_val, rhs.position()), )?), } } else { @@ -945,9 +938,13 @@ impl Engine<'_> { } // dot_lhs.dot_rhs = rhs - Expr::Dot(dot_lhs, dot_rhs, _) => { - self.set_dot_val(scope, dot_lhs, dot_rhs, rhs_val, rhs.position(), *op_pos) - } + Expr::Dot(dot_lhs, dot_rhs, _) => self.set_dot_val( + scope, + dot_lhs, + dot_rhs, + (&mut rhs_val, rhs.position()), + *op_pos, + ), // Error assignment to constant expr if expr.is_constant() => Err(EvalAltResult::ErrorAssignmentToConstant( @@ -973,45 +970,111 @@ impl Engine<'_> { Ok(Box::new(arr)) } - // Dump AST - Expr::FunctionCall(fn_name, args_expr_list, _, pos) if fn_name == KEYWORD_DUMP_AST => { - let pos = if args_expr_list.len() == 0 { - *pos - } else { - args_expr_list[0].position() - }; - - // Change the argument to a debug dump of the expressions - let result = args_expr_list - .into_iter() - .map(|expr| format!("{:#?}", expr)) - .collect::>() - .join("\n"); - - // Redirect call to `print` - self.call_fn_raw( - scope, - KEYWORD_PRINT, - vec![result.into_dynamic().as_mut()], - None, - pos, - ) - } - - // Normal function call Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => { - let mut values = args_expr_list - .iter() - .map(|expr| self.eval_expr(scope, expr)) - .collect::, _>>()?; + // Has a system function an override? + fn has_override(engine: &Engine, name: &str) -> bool { + let spec = FnSpec { + name: name.into(), + args: Some(vec![TypeId::of::()]), + }; - self.call_fn_raw( - scope, - fn_name, - values.iter_mut().map(|b| b.as_mut()).collect(), - def_val.as_ref(), - *pos, - ) + engine.ext_functions.contains_key(&spec) + || engine + .script_functions + .binary_search_by(|f| f.compare(name, 1)) + .is_ok() + } + + match fn_name.as_str() { + // Dump AST + KEYWORD_DUMP_AST => { + let pos = if args_expr_list.len() == 0 { + *pos + } else { + args_expr_list[0].position() + }; + + // Change the argument to a debug dump of the expressions + let result = args_expr_list + .into_iter() + .map(|expr| format!("{:#?}", expr)) + .collect::>() + .join("\n"); + + // Redirect call to `print` + self.call_fn_raw( + KEYWORD_PRINT, + vec![result.into_dynamic().as_mut()], + None, + pos, + ) + } + + // type_of + KEYWORD_TYPE_OF + if args_expr_list.len() == 1 && !has_override(self, KEYWORD_TYPE_OF) => + { + let r = self.eval_expr(scope, &args_expr_list[0])?; + Ok(self + .map_type_name((*r).type_name()) + .to_string() + .into_dynamic()) + } + + // eval + KEYWORD_EVAL + if args_expr_list.len() == 1 && !has_override(self, KEYWORD_EVAL) => + { + let pos = args_expr_list[0].position(); + let r = self.eval_expr(scope, &args_expr_list[0])?; + + let script = + r.downcast_ref::() + .map(String::as_str) + .ok_or_else(|| { + EvalAltResult::ErrorMismatchOutputType( + r.type_name().into(), + pos, + ) + })?; + + #[cfg(not(feature = "no_optimize"))] + let ast = { + let orig_optimization_level = self.optimization_level; + + self.set_optimization_level(OptimizationLevel::None); + let ast = self.compile(script); + self.set_optimization_level(orig_optimization_level); + + ast.map_err(EvalAltResult::ErrorParsing)? + }; + + #[cfg(feature = "no_optimize")] + let ast = self.compile(script).map_err(EvalAltResult::ErrorParsing)?; + + return Ok(self.eval_ast_with_scope_raw(scope, true, &ast).map_err( + |mut err| { + err.set_position(pos); + err + }, + )?); + } + + // Normal function call + _ => { + let mut values = args_expr_list + .iter() + .map(|expr| self.eval_expr(scope, expr)) + .collect::, _>>()?; + + self.call_fn_raw( + fn_name, + values.iter_mut().map(|b| b.as_mut()).collect(), + def_val.as_ref(), + *pos, + ) + } + } } Expr::And(lhs, rhs) => Ok(Box::new( diff --git a/src/scope.rs b/src/scope.rs index b7d43c97..a40a2589 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -31,6 +31,13 @@ pub struct ScopeEntry<'a> { pub expr: Option, } +/// Information about a particular entry in the Scope. +pub(crate) struct ScopeSource<'a> { + pub name: &'a str, + pub idx: usize, + pub var_type: VariableType, +} + /// A type containing information about the current scope. /// Useful for keeping state between `Engine` evaluation runs. /// @@ -150,7 +157,7 @@ impl<'a> Scope<'a> { } /// Find a variable in the Scope, starting from the last. - pub fn get(&self, key: &str) -> Option<(usize, &str, VariableType, Dynamic)> { + pub(crate) fn get(&self, key: &str) -> Option<(ScopeSource, Dynamic)> { self.0 .iter() .enumerate() @@ -165,7 +172,16 @@ impl<'a> Scope<'a> { value, .. }, - )| (i, name.as_ref(), *var_type, value.clone()), + )| { + ( + ScopeSource { + name: name.as_ref(), + idx: i, + var_type: *var_type, + }, + value.clone(), + ) + }, ) } @@ -184,11 +200,11 @@ impl<'a> Scope<'a> { pub(crate) fn get_mut(&mut self, name: &str, index: usize) -> &mut Dynamic { let entry = self.0.get_mut(index).expect("invalid index in Scope"); - assert_ne!( - entry.var_type, - VariableType::Constant, - "get mut of constant variable" - ); + // assert_ne!( + // entry.var_type, + // VariableType::Constant, + // "get mut of constant variable" + // ); assert_eq!(entry.name, name, "incorrect key at Scope entry"); &mut entry.value diff --git a/tests/get_set.rs b/tests/get_set.rs index 7ea064b1..a1c64d25 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -91,7 +91,7 @@ fn test_big_get_set() -> Result<(), EvalAltResult> { ); assert_eq!( - engine.eval::("let a = new_tp(); a.type_of()")?, + engine.eval::("let a = new_tp(); type_of(a)")?, "TestParent" ); diff --git a/tests/side_effects.rs b/tests/side_effects.rs new file mode 100644 index 00000000..edfc0009 --- /dev/null +++ b/tests/side_effects.rs @@ -0,0 +1,39 @@ +use rhai::{Engine, EvalAltResult, RegisterFn, Scope}; +use std::cell::Cell; +use std::rc::Rc; + +#[derive(Debug, Clone)] +struct CommandWrapper { + value: Rc>, +} + +impl CommandWrapper { + pub fn set_value(&mut self, x: i64) { + let val = self.value.get(); + self.value.set(val + x); + } +} + +#[test] +fn test_side_effects() -> Result<(), EvalAltResult> { + let mut engine = Engine::new(); + let mut scope = Scope::new(); + + let payload = Rc::new(Cell::new(12)); + assert_eq!(payload.get(), 12); + + let command = CommandWrapper { + value: payload.clone(), + }; + + scope.push_constant("Command", command); + + engine.register_type_with_name::("CommandType"); + engine.register_fn("action", CommandWrapper::set_value); + + engine.eval_with_scope::<()>(&mut scope, "Command.action(30)")?; + + assert_eq!(payload.get(), 42); + + Ok(()) +} diff --git a/tests/types.rs b/tests/types.rs index fd9cda32..0101a120 100644 --- a/tests/types.rs +++ b/tests/types.rs @@ -23,10 +23,10 @@ fn test_type_of() -> Result<(), EvalAltResult> { assert_eq!(engine.eval::(r#"type_of("hello")"#)?, "string"); #[cfg(not(feature = "only_i32"))] - assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i64"); + assert_eq!(engine.eval::("let x = 123; type_of(x)")?, "i64"); #[cfg(feature = "only_i32")] - assert_eq!(engine.eval::("let x = 123; x.type_of()")?, "i32"); + assert_eq!(engine.eval::("let x = 123; type_of(x)")?, "i32"); Ok(()) }