From 6a6c5f30de4488b7b4e40f7c562b04e49e356bf8 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 Mar 2020 19:53:42 +0800 Subject: [PATCH] Add eval function. --- README.md | 35 +++++++++++++++ src/api.rs | 117 +++++++++++++++++++++++++++++++----------------- src/engine.rs | 108 +++++++++++++++++++++++++++++++++----------- src/optimize.rs | 22 ++++++--- src/parser.rs | 36 +++++++-------- src/scope.rs | 4 +- 6 files changed, 229 insertions(+), 93 deletions(-) diff --git a/README.md b/README.md index a53651d9..cc21e307 100644 --- a/README.md +++ b/README.md @@ -1515,3 +1515,38 @@ let engine = rhai::Engine::new(); // Turn off the optimizer engine.set_optimization_level(rhai::OptimizationLevel::None); ``` + +`eval` - or "How to Shoot Yourself in the Foot even Easier" +--------------------------------------------------------- + +Saving the best for last: in addition to script optimizations, there is the ever-dreaded... `eval` function! + +```rust +let x = 10; + +fn foo(x) { x += 12; x } + +let script = "let y = x;"; // build a script +script += "y += foo(y);"; +script += "x + y"; + +let result = eval(script); + +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) + +eval("{ let z = y }"); // to keep a variable local, use a statement block + +print("z = " + z); // error - variable 'z' not found +``` + +For those who subscribe to the (very sensible) motto of ["`eval` is **evil**"](http://linterrors.com/js/eval-is-evil), +disable `eval` by overriding it, probably with something that throws. + +```rust +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" +``` diff --git a/src/api.rs b/src/api.rs index 34be8687..d6f592ba 100644 --- a/src/api.rs +++ b/src/api.rs @@ -2,7 +2,7 @@ use crate::any::{Any, AnyExt, Dynamic}; use crate::call::FuncArgs; -use crate::engine::{Engine, FnAny, FnSpec}; +use crate::engine::{Engine, FnAny, FnSpec, FUNC_GETTER, FUNC_SETTER}; use crate::error::ParseError; use crate::fn_register::RegisterFn; use crate::parser::{lex, parse, FnDef, Position, AST}; @@ -173,8 +173,8 @@ impl<'e> Engine<'e> { name: &str, callback: impl Fn(&mut T) -> U + 'static, ) { - let get_name = "get$".to_string() + name; - self.register_fn(&get_name, callback); + let get_fn_name = format!("{}{}", FUNC_GETTER, name); + self.register_fn(&get_fn_name, callback); } /// Register a setter function for a member of a registered type with the `Engine`. @@ -215,8 +215,8 @@ impl<'e> Engine<'e> { name: &str, callback: impl Fn(&mut T, U) -> () + 'static, ) { - let set_name = "set$".to_string() + name; - self.register_fn(&set_name, callback); + let set_fn_name = format!("{}{}", FUNC_SETTER, name); + self.register_fn(&set_fn_name, callback); } /// Shorthand for registering both getter and setter functions @@ -264,7 +264,7 @@ impl<'e> Engine<'e> { self.register_set(name, set_fn); } - /// Compile a string into an `AST`, which can be used later for evaluations. + /// Compile a string into an `AST`, which can be used later for evaluation. /// /// # Example /// @@ -274,7 +274,7 @@ impl<'e> Engine<'e> { /// /// let mut engine = Engine::new(); /// - /// // Compile a script to an AST and store it for later evaluations + /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("40 + 2")?; /// /// for _ in 0..42 { @@ -287,27 +287,40 @@ impl<'e> Engine<'e> { self.compile_with_scope(&Scope::new(), input) } - /// Compile a string into an `AST` using own scope, which can be used later for evaluations. - /// The scope is useful for passing constants into the script for optimization. + /// Compile a string into an `AST` using own scope, which can be used later for evaluation. + /// The scope is useful for passing constants into the script for optimization + /// when using `OptimizationLevel::Full`. /// /// # Example /// /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// use rhai::{Engine, Scope}; + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; /// /// let mut engine = Engine::new(); /// + /// // Set optimization level to 'Full' so the Engine can fold constants + /// // into function calls and operators. + /// engine.set_optimization_level(OptimizationLevel::Full); + /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant /// - /// // Compile a script to an AST and store it for later evaluations + /// // Compile a script to an AST and store it for later evaluation. + /// // Notice that `Full` optimization is on, so constants are folded + /// // into function calls and operators. /// let ast = engine.compile_with_scope(&mut scope, - /// "if x > 40 { x } else { 0 }" + /// "if x > 40 { x } else { 0 }" // all 'x' are replaced with 42 /// )?; /// + /// // Normally this would have failed because no scope is passed into the 'eval_ast' + /// // call and so the variable 'x' does not exist. Here, it passes because the script + /// // has been optimized and all references to 'x' are already gone. /// assert_eq!(engine.eval_ast::(&ast)?, 42); + /// # } /// # Ok(()) /// # } /// ``` @@ -329,7 +342,7 @@ impl<'e> Engine<'e> { .map(|_| contents) } - /// Compile a script file into an `AST`, which can be used later for evaluations. + /// Compile a script file into an `AST`, which can be used later for evaluation. /// /// # Example /// @@ -339,7 +352,7 @@ impl<'e> Engine<'e> { /// /// let mut engine = Engine::new(); /// - /// // Compile a script file to an AST and store it for later evaluations + /// // Compile a script file to an AST and store it for later evaluation. /// // Notice that a PathBuf is required which can easily be constructed from a string. /// let ast = engine.compile_file("script.rhai".into())?; /// @@ -354,26 +367,33 @@ impl<'e> Engine<'e> { self.compile_file_with_scope(&Scope::new(), path) } - /// Compile a script file into an `AST` using own scope, which can be used later for evaluations. - /// The scope is useful for passing constants into the script for optimization. + /// Compile a script file into an `AST` using own scope, which can be used later for evaluation. + /// The scope is useful for passing constants into the script for optimization + /// when using `OptimizationLevel::Full`. /// /// # Example /// /// ```no_run /// # fn main() -> Result<(), rhai::EvalAltResult> { - /// use rhai::{Engine, Scope}; + /// # #[cfg(not(feature = "no_optimize"))] + /// # { + /// use rhai::{Engine, Scope, OptimizationLevel}; /// /// let mut engine = Engine::new(); /// + /// // Set optimization level to 'Full' so the Engine can fold constants. + /// engine.set_optimization_level(OptimizationLevel::Full); + /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push_constant("x", 42_i64); // 'x' is a constant /// - /// // Compile a script to an AST and store it for later evaluations + /// // Compile a script to an AST and store it for later evaluation. /// // Notice that a PathBuf is required which can easily be constructed from a string. /// let ast = engine.compile_file_with_scope(&mut scope, "script.rhai".into())?; /// /// let result = engine.eval_ast::(&ast)?; + /// # } /// # Ok(()) /// # } /// ``` @@ -491,13 +511,13 @@ impl<'e> Engine<'e> { /// /// # Example /// - /// ```no_run + /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::Engine; /// /// let mut engine = Engine::new(); /// - /// // Compile a script to an AST and store it for later evaluations + /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("40 + 2")?; /// /// // Evaluate it @@ -514,22 +534,25 @@ impl<'e> Engine<'e> { /// /// # Example /// - /// ```no_run + /// ``` /// # fn main() -> Result<(), rhai::EvalAltResult> { /// use rhai::{Engine, Scope}; /// /// let mut engine = Engine::new(); /// - /// // Compile a script to an AST and store it for later evaluations + /// // Compile a script to an AST and store it for later evaluation /// let ast = engine.compile("x + 2")?; /// /// // Create initialized scope /// let mut scope = Scope::new(); /// scope.push("x", 40_i64); /// + /// // Compile a script to an AST and store it for later evaluation + /// let ast = engine.compile("x = x + 2; x")?; + /// /// // Evaluate it - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x = x + 2; x")?, 42); - /// assert_eq!(engine.eval_with_scope::(&mut scope, "x = x + 2; x")?, 44); + /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 42); + /// assert_eq!(engine.eval_ast_with_scope::(&mut scope, &ast)?, 44); /// /// // The variable in the scope is modified /// assert_eq!(scope.get_value::("x").expect("variable x should exist"), 44); @@ -541,12 +564,32 @@ impl<'e> Engine<'e> { scope: &mut Scope, ast: &AST, ) -> Result { + self.eval_ast_with_scope_raw(scope, false, ast) + .and_then(|out| { + out.downcast::().map(|v| *v).map_err(|a| { + EvalAltResult::ErrorMismatchOutputType( + self.map_type_name((*a).type_name()).to_string(), + Position::none(), + ) + }) + }) + } + + pub(crate) fn eval_ast_with_scope_raw( + &mut self, + scope: &mut Scope, + retain_functions: bool, + ast: &AST, + ) -> Result { fn eval_ast_internal( engine: &mut Engine, scope: &mut Scope, + retain_functions: bool, ast: &AST, ) -> Result { - engine.clear_functions(); + if !retain_functions { + engine.clear_functions(); + } let statements = { let AST(statements, functions) = ast; @@ -560,24 +603,17 @@ impl<'e> Engine<'e> { result = engine.eval_stmt(scope, stmt)?; } - engine.clear_functions(); + if !retain_functions { + engine.clear_functions(); + } Ok(result) } - eval_ast_internal(self, scope, ast) - .or_else(|err| match err { - EvalAltResult::Return(out, _) => Ok(out), - _ => Err(err), - }) - .and_then(|out| { - out.downcast::().map(|v| *v).map_err(|a| { - EvalAltResult::ErrorMismatchOutputType( - self.map_type_name((*a).type_name()).to_string(), - Position::eof(), - ) - }) - }) + eval_ast_internal(self, scope, retain_functions, ast).or_else(|err| match err { + EvalAltResult::Return(out, _) => Ok(out), + _ => Err(err), + }) } /// Evaluate a file, but throw away the result and only return error (if any). @@ -729,9 +765,10 @@ 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(name, values, None, Position::none()); + let result = engine.call_fn_raw(&mut scope, name, values, None, Position::none()); result } diff --git a/src/engine.rs b/src/engine.rs index 9ea647f8..bd389312 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -38,6 +38,7 @@ pub(crate) const KEYWORD_PRINT: &'static str = "print"; pub(crate) const KEYWORD_DEBUG: &'static str = "debug"; pub(crate) const KEYWORD_DUMP_AST: &'static str = "dump_ast"; pub(crate) const KEYWORD_TYPE_OF: &'static str = "type_of"; +pub(crate) const KEYWORD_EVAL: &'static str = "eval"; pub(crate) const FUNC_GETTER: &'static str = "get$"; pub(crate) const FUNC_SETTER: &'static str = "set$"; @@ -160,6 +161,7 @@ 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>, @@ -185,10 +187,15 @@ impl Engine<'_> { // Evaluate // Convert return statement to return value - return match self.eval_stmt(&mut scope, &fn_def.body) { - Err(EvalAltResult::Return(x, _)) => Ok(x), - other => other, - }; + return self + .eval_stmt(&mut scope, &fn_def.body) + .or_else(|mut err| match err { + EvalAltResult::Return(x, _) => Ok(x), + _ => { + err.set_position(pos); + Err(err) + } + }); } let spec = FnSpec { @@ -196,24 +203,26 @@ impl Engine<'_> { args: Some(args.iter().map(|a| Any::type_id(&**a)).collect()), }; + // Argument must be a string + fn cast_to_string<'a>(r: &'a Variant, pos: Position) -> Result<&'a str, EvalAltResult> { + r.downcast_ref::() + .map(String::as_str) + .ok_or_else(|| EvalAltResult::ErrorMismatchOutputType(r.type_name().into(), pos)) + } + // Search built-in's and external functions if let Some(func) = self.ext_functions.get(&spec) { // Run external function let result = func(args, pos)?; // See if the function match print/debug (which requires special processing) - let callback = match fn_name { - KEYWORD_PRINT => self.on_print.as_mut(), - KEYWORD_DEBUG => self.on_debug.as_mut(), - _ => return Ok(result), - }; + match fn_name { + KEYWORD_PRINT => self.on_print.as_mut()(cast_to_string(result.as_ref(), pos)?), + KEYWORD_DEBUG => self.on_debug.as_mut()(cast_to_string(result.as_ref(), pos)?), + _ => (), + } - let val = &result - .downcast::() - .map(|s| *s) - .unwrap_or("error: not a string".into()); - - return Ok(callback(val).into_dynamic()); + return Ok(result); } if fn_name == KEYWORD_TYPE_OF && args.len() == 1 { @@ -224,6 +233,32 @@ impl Engine<'_> { .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( @@ -283,14 +318,14 @@ impl Engine<'_> { .chain(values.iter_mut().map(Dynamic::as_mut)) .collect(); - self.call_fn_raw(fn_name, args, def_val.as_ref(), *pos) + self.call_fn_raw(scope, 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(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos) } // xxx.idx_lhs[idx_expr] @@ -301,7 +336,7 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); ( - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)?, *pos, ) } @@ -329,7 +364,7 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + 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)) } // xxx.idx_lhs[idx_expr].rhs @@ -340,7 +375,7 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); ( - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos)?, + self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos)?, *pos, ) } @@ -642,7 +677,13 @@ impl Engine<'_> { Expr::Property(id, pos) => { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - self.call_fn_raw(&set_fn_name, vec![this_ptr, new_val.as_mut()], None, *pos) + self.call_fn_raw( + scope, + &set_fn_name, + vec![this_ptr, new_val.as_mut()], + None, + *pos, + ) } // xxx.lhs[idx_expr] @@ -653,14 +694,20 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(scope, &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) }) .and_then(|mut v| { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) + self.call_fn_raw( + scope, + &set_fn_name, + vec![this_ptr, v.as_mut()], + None, + *pos, + ) }) } @@ -677,7 +724,7 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(scope, &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) .map(|_| v) // Discard Ok return value @@ -685,7 +732,13 @@ impl Engine<'_> { .and_then(|mut v| { let set_fn_name = format!("{}{}", FUNC_SETTER, id); - self.call_fn_raw(&set_fn_name, vec![this_ptr, v.as_mut()], None, *pos) + self.call_fn_raw( + scope, + &set_fn_name, + vec![this_ptr, v.as_mut()], + None, + *pos, + ) }) } @@ -697,7 +750,7 @@ impl Engine<'_> { Expr::Property(id, pos) => { let get_fn_name = format!("{}{}", FUNC_GETTER, id); - self.call_fn_raw(&get_fn_name, vec![this_ptr], None, *pos) + self.call_fn_raw(scope, &get_fn_name, vec![this_ptr], None, *pos) .and_then(|v| { let idx = self.eval_index_value(scope, idx_expr)?; let (mut target, _) = @@ -718,6 +771,7 @@ 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, @@ -936,6 +990,7 @@ impl Engine<'_> { // Redirect call to `print` self.call_fn_raw( + scope, KEYWORD_PRINT, vec![result.into_dynamic().as_mut()], None, @@ -951,6 +1006,7 @@ impl Engine<'_> { .collect::, _>>()?; self.call_fn_raw( + scope, fn_name, values.iter_mut().map(|b| b.as_mut()).collect(), def_val.as_ref(), diff --git a/src/optimize.rs b/src/optimize.rs index 7ac461ec..35f22957 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -2,7 +2,8 @@ use crate::any::{Any, Dynamic}; use crate::engine::{ - Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT, KEYWORD_TYPE_OF, + Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_EVAL, KEYWORD_PRINT, + KEYWORD_TYPE_OF, }; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::scope::{Scope, ScopeEntry, VariableType}; @@ -287,7 +288,7 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - /// Optimize an expression. fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // These keywords are handled specially - const SKIP_FUNC_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_DUMP_AST]; + const DONT_EVAL_KEYWORDS: [&str; 3] = [KEYWORD_PRINT, KEYWORD_DEBUG, KEYWORD_EVAL]; match expr { // ( stmt ) @@ -431,10 +432,16 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { ), }, - // Do not optimize anything within built-in function keywords - Expr::FunctionCall(id, args, def_value, pos) if SKIP_FUNC_KEYWORDS.contains(&id.as_str())=> + // Do not optimize anything within dump_ast + Expr::FunctionCall(id, args, def_value, pos) if id == KEYWORD_DUMP_AST => Expr::FunctionCall(id, args, def_value, pos), + // Do not optimize anything within built-in function keywords + Expr::FunctionCall(id, args, def_value, pos) if DONT_EVAL_KEYWORDS.contains(&id.as_str())=> + Expr::FunctionCall(id, + args.into_iter().map(|a| optimize_expr(a, state)).collect(), + def_value, pos), + // Eagerly call functions Expr::FunctionCall(id, args, def_value, pos) if state.engine.optimization_level == OptimizationLevel::Full // full optimizations @@ -467,9 +474,13 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { }) ).flatten().unwrap_or_else(|| Expr::FunctionCall(id, args, def_value, pos)) } + // id(args ..) -> optimize function call arguments Expr::FunctionCall(id, args, def_value, pos) => - Expr::FunctionCall(id, args.into_iter().map(|a| optimize_expr(a, state)).collect(), def_value, pos), + Expr::FunctionCall(id, + args.into_iter().map(|a| optimize_expr(a, state)).collect(), + def_value, pos), + // constant-name Expr::Variable(ref name, _) if state.contains_constant(name) => { state.set_dirty(); @@ -480,6 +491,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { .expect("should find constant in scope!") .clone() } + // All other expressions - skip expr => expr, } diff --git a/src/parser.rs b/src/parser.rs index 0f62ee1e..10abc3a8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -469,6 +469,10 @@ pub enum Token { UnaryMinus, Multiply, Divide, + Modulo, + PowerOf, + LeftShift, + RightShift, SemiColon, Colon, Comma, @@ -482,15 +486,18 @@ pub enum Token { Else, While, Loop, + For, + In, LessThan, GreaterThan, - Bang, LessThanEqualsTo, GreaterThanEqualsTo, EqualsTo, NotEqualsTo, + Bang, Pipe, Or, + XOr, Ampersand, And, #[cfg(not(feature = "no_function"))] @@ -507,15 +514,8 @@ pub enum Token { AndAssign, OrAssign, XOrAssign, - LeftShift, - RightShift, - XOr, - Modulo, ModuloAssign, - PowerOf, PowerOfAssign, - For, - In, LexError(LexError), } @@ -2210,31 +2210,27 @@ fn parse_stmt<'a>( Ok(Stmt::Break(pos)) } (Token::Break, pos) => return Err(ParseError::new(PERR::LoopBreak, *pos)), - (token @ Token::Return, _) | (token @ Token::Throw, _) => { + (token @ Token::Return, pos) | (token @ Token::Throw, pos) => { let return_type = match token { Token::Return => ReturnType::Return, Token::Throw => ReturnType::Exception, _ => panic!("token should be return or throw"), }; + let pos = *pos; input.next(); match input.peek() { // `return`/`throw` at EOF None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), // `return;` or `throw;` - Some((Token::SemiColon, pos)) => { - let pos = *pos; - Ok(Stmt::ReturnWithVal(None, return_type, pos)) - } + Some((Token::SemiColon, _)) => Ok(Stmt::ReturnWithVal(None, return_type, pos)), // `return` or `throw` with expression - Some((_, pos)) => { - let pos = *pos; - Ok(Stmt::ReturnWithVal( - Some(Box::new(parse_expr(input)?)), - return_type, - pos, - )) + Some((_, _)) => { + let expr = parse_expr(input)?; + let pos = expr.position(); + + Ok(Stmt::ReturnWithVal(Some(Box::new(expr)), return_type, pos)) } } } diff --git a/src/scope.rs b/src/scope.rs index 6cb1c4af..dcd35091 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -71,7 +71,7 @@ impl<'a> Scope<'a> { } /// Add (push) a new variable to the Scope. - pub fn push>, T: Any>(&mut self, name: K, value: T) { + pub fn push>, T: Any + Clone>(&mut self, name: K, value: T) { let value = value.into_dynamic(); // Map into constant expressions @@ -91,7 +91,7 @@ impl<'a> Scope<'a> { /// Constants propagation is a technique used to optimize an AST. /// However, in order to be used for optimization, constants must be in one of the recognized types: /// `INT` (default to `i64`, `i32` if `only_i32`), `f64`, `String`, `char` and `bool`. - pub fn push_constant>, T: Any>(&mut self, name: K, value: T) { + pub fn push_constant>, T: Any + Clone>(&mut self, name: K, value: T) { let value = value.into_dynamic(); // Map into constant expressions