diff --git a/src/engine.rs b/src/engine.rs index 5eb11702..e8cf5c3c 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -228,11 +228,6 @@ impl> From for Target<'_> { /// [INTERNALS] A type that holds all the current states of the Engine. /// Exported under the `internals` feature only. /// -/// # Safety -/// -/// This type uses some unsafe code, mainly for avoiding cloning of local variable names via -/// direct lifetime casting. -/// /// ## WARNING /// /// This type is volatile and may change. @@ -1495,6 +1490,12 @@ impl Engine { } /// Evaluate a statement + /// + /// + /// # Safety + /// + /// This method uses some unsafe code, mainly for avoiding cloning of local variable names via + /// direct lifetime casting. pub(crate) fn eval_stmt( &self, scope: &mut Scope, @@ -1538,7 +1539,7 @@ impl Engine { // If-else statement Stmt::IfThenElse(x) => { - let (expr, if_block, else_block) = x.as_ref(); + let (expr, if_block, else_block, _) = x.as_ref(); self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .as_bool() @@ -1556,7 +1557,7 @@ impl Engine { // While loop Stmt::While(x) => loop { - let (expr, body) = x.as_ref(); + let (expr, body, _) = x.as_ref(); match self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? @@ -1582,8 +1583,8 @@ impl Engine { }, // Loop statement - Stmt::Loop(body) => loop { - match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) { + Stmt::Loop(x) => loop { + match self.eval_stmt(scope, mods, state, lib, this_ptr, &x.0, level) { Ok(_) => (), Err(err) => match *err { EvalAltResult::ErrorLoopBreak(false, _) => (), @@ -1595,7 +1596,7 @@ impl Engine { // For loop Stmt::For(x) => { - let (name, expr, stmt) = x.as_ref(); + let (name, expr, stmt, _) = x.as_ref(); let iter_type = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let tid = iter_type.type_id(); @@ -1641,16 +1642,9 @@ impl Engine { // Return value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Return => { + let expr = x.1.as_ref().unwrap(); Err(Box::new(EvalAltResult::Return( - self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - x.1.as_ref().unwrap(), - level, - )?, + self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?, (x.0).1, ))) } @@ -1662,15 +1656,8 @@ impl Engine { // Throw value Stmt::ReturnWithVal(x) if x.1.is_some() && (x.0).0 == ReturnType::Exception => { - let val = self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - x.1.as_ref().unwrap(), - level, - )?; + let expr = x.1.as_ref().unwrap(); + let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; Err(Box::new(EvalAltResult::ErrorRuntime( val.take_string().unwrap_or_else(|_| "".into()), (x.0).1, @@ -1686,23 +1673,16 @@ impl Engine { // Let statement Stmt::Let(x) if x.1.is_some() => { - let ((var_name, _), expr) = x.as_ref(); - let val = self.eval_expr( - scope, - mods, - state, - lib, - this_ptr, - expr.as_ref().unwrap(), - level, - )?; + let ((var_name, _), expr, _) = x.as_ref(); + let expr = expr.as_ref().unwrap(); + let val = self.eval_expr(scope, mods, state, lib, this_ptr, expr, level)?; let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Normal, val, false); Ok(Default::default()) } Stmt::Let(x) => { - let ((var_name, _), _) = x.as_ref(); + let ((var_name, _), _, _) = x.as_ref(); let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push(var_name, ()); Ok(Default::default()) @@ -1710,7 +1690,7 @@ impl Engine { // Const statement Stmt::Const(x) if x.1.is_constant() => { - let ((var_name, _), expr) = x.as_ref(); + let ((var_name, _), expr, _) = x.as_ref(); let val = self.eval_expr(scope, mods, state, lib, this_ptr, &expr, level)?; let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); scope.push_dynamic_value(var_name, ScopeEntryType::Constant, val, true); @@ -1723,7 +1703,7 @@ impl Engine { // Import statement #[cfg(not(feature = "no_module"))] Stmt::Import(x) => { - let (expr, (name, _pos)) = x.as_ref(); + let (expr, (name, _pos), _) = x.as_ref(); // Guard against too many modules #[cfg(not(feature = "unchecked"))] @@ -1756,8 +1736,8 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] - Stmt::Export(list) => { - for ((id, id_pos), rename) in list.iter() { + Stmt::Export(x) => { + for ((id, id_pos), rename) in x.0.iter() { // Mark scope variables as public if let Some(index) = scope.get_index(id).map(|(i, _)| i) { let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); diff --git a/src/optimize.rs b/src/optimize.rs index bf5f6cd3..27f6f816 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -185,6 +185,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { optimize_expr(expr, state), optimize_stmt(x.1, state, true), None, + x.3, ))), }, // if expr { if_block } else { else_block } @@ -201,6 +202,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(_) => None, // Noop -> no else block stmt => Some(stmt), }, + x.3, ))), }, // while expr { block } @@ -211,7 +213,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(pos) } // while true { block } -> loop { block } - Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(x.1, state, false))), + Expr::True(_) => Stmt::Loop(Box::new((optimize_stmt(x.1, state, false), x.2))), // while expr { block } expr => match optimize_stmt(x.1, state, false) { // while expr { break; } -> { expr; } @@ -226,11 +228,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Block(Box::new((statements, pos))) } // while expr { block } - stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt))), + stmt => Stmt::While(Box::new((optimize_expr(expr, state), stmt, x.2))), }, }, // loop { block } - Stmt::Loop(block) => match optimize_stmt(*block, state, false) { + Stmt::Loop(x) => match optimize_stmt(x.0, state, false) { // loop { break; } -> Noop Stmt::Break(pos) => { // Only a single break statement @@ -238,23 +240,26 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { Stmt::Noop(pos) } // loop { block } - stmt => Stmt::Loop(Box::new(stmt)), + stmt => Stmt::Loop(Box::new((stmt, x.1))), }, // for id in expr { block } Stmt::For(x) => Stmt::For(Box::new(( x.0, optimize_expr(x.1, state), optimize_stmt(x.2, state, false), + x.3, ))), // let id = expr; - Stmt::Let(x) if x.1.is_some() => { - Stmt::Let(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) - } + Stmt::Let(x) if x.1.is_some() => Stmt::Let(Box::new(( + x.0, + Some(optimize_expr(x.1.unwrap(), state)), + x.2, + ))), // let id; stmt @ Stmt::Let(_) => stmt, // import expr as id; #[cfg(not(feature = "no_module"))] - Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1))), + Stmt::Import(x) => Stmt::Import(Box::new((optimize_expr(x.0, state), x.1, x.2))), // { block } Stmt::Block(x) => { let orig_len = x.0.len(); // Original number of statements in the block, for change detection @@ -267,7 +272,7 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { .map(|stmt| match stmt { // Add constant into the state Stmt::Const(v) => { - let ((name, pos), expr) = *v; + let ((name, pos), expr, _) = *v; state.push_constant(&name, expr); state.set_dirty(); Stmt::Noop(pos) // No need to keep constants @@ -367,9 +372,11 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { // expr; Stmt::Expr(expr) => Stmt::Expr(Box::new(optimize_expr(*expr, state))), // return expr; - Stmt::ReturnWithVal(x) if x.1.is_some() => { - Stmt::ReturnWithVal(Box::new((x.0, Some(optimize_expr(x.1.unwrap(), state))))) - } + Stmt::ReturnWithVal(x) if x.1.is_some() => Stmt::ReturnWithVal(Box::new(( + x.0, + Some(optimize_expr(x.1.unwrap(), state)), + x.2, + ))), // All other statements - skip stmt => stmt, } @@ -412,7 +419,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let pos = m.1; m.0.into_iter().find(|((name, _), _)| name.as_str() == prop.as_str()) - .map(|(_, expr)| expr.set_position(pos)) + .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // lhs.rhs @@ -429,7 +436,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // Array literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - a.0.take(i.0 as usize).set_position(a.1) + let mut expr = a.0.take(i.0 as usize); + expr.set_position(a.1); + expr } // map[string] (Expr::Map(m), Expr::StringConstant(s)) if m.0.iter().all(|(_, x)| x.is_pure()) => { @@ -438,7 +447,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let pos = m.1; m.0.into_iter().find(|((name, _), _)| *name == s.0) - .map(|(_, expr)| expr.set_position(pos)) + .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] @@ -625,7 +634,9 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); // Replace constant with value - state.find_constant(&name).unwrap().clone().set_position(pos) + let mut expr = state.find_constant(&name).unwrap().clone(); + expr.set_position(pos); + expr } // Custom syntax @@ -687,7 +698,7 @@ fn optimize( match &stmt { Stmt::Const(v) => { // Load constants - let ((name, _), expr) = v.as_ref(); + let ((name, _), expr, _) = v.as_ref(); state.push_constant(&name, expr.clone()); stmt // Keep it in the global scope } diff --git a/src/parser.rs b/src/parser.rs index 1177c3c2..417c35fb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -26,7 +26,6 @@ use crate::stdlib::{ fmt, format, hash::{Hash, Hasher}, iter::empty, - mem, num::NonZeroUsize, ops::Add, string::{String, ToString}, @@ -511,33 +510,38 @@ pub enum Stmt { /// No-op. Noop(Position), /// if expr { stmt } else { stmt } - IfThenElse(Box<(Expr, Stmt, Option)>), + IfThenElse(Box<(Expr, Stmt, Option, Position)>), /// while expr { stmt } - While(Box<(Expr, Stmt)>), + While(Box<(Expr, Stmt, Position)>), /// loop { stmt } - Loop(Box), + Loop(Box<(Stmt, Position)>), /// for id in expr { stmt } - For(Box<(String, Expr, Stmt)>), + For(Box<(String, Expr, Stmt, Position)>), /// let id = expr - Let(Box<((String, Position), Option)>), + Let(Box<((String, Position), Option, Position)>), /// const id = expr - Const(Box<((String, Position), Expr)>), + Const(Box<((String, Position), Expr, Position)>), /// { stmt; ... } Block(Box<(StaticVec, Position)>), - /// { stmt } + /// expr Expr(Box), /// continue Continue(Position), /// break Break(Position), /// return/throw - ReturnWithVal(Box<((ReturnType, Position), Option)>), + ReturnWithVal(Box<((ReturnType, Position), Option, Position)>), /// import expr as module #[cfg(not(feature = "no_module"))] - Import(Box<(Expr, (String, Position))>), + Import(Box<(Expr, (String, Position), Position)>), /// expr id as name, ... #[cfg(not(feature = "no_module"))] - Export(Box)>>), + Export( + Box<( + StaticVec<((String, Position), Option<(String, Position)>)>, + Position, + )>, + ), } impl Default for Stmt { @@ -555,19 +559,44 @@ impl Stmt { Stmt::Const(x) => (x.0).1, Stmt::ReturnWithVal(x) => (x.0).1, Stmt::Block(x) => x.1, - Stmt::IfThenElse(x) => x.0.position(), + Stmt::IfThenElse(x) => x.3, Stmt::Expr(x) => x.position(), - Stmt::While(x) => x.1.position(), - Stmt::Loop(x) => x.position(), - Stmt::For(x) => x.2.position(), + Stmt::While(x) => x.2, + Stmt::Loop(x) => x.1, + Stmt::For(x) => x.3, #[cfg(not(feature = "no_module"))] - Stmt::Import(x) => (x.1).1, + Stmt::Import(x) => x.2, #[cfg(not(feature = "no_module"))] - Stmt::Export(x) => (x.get(0).0).1, + Stmt::Export(x) => x.1, } } + /// Override the `Position` of this statement. + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Stmt::Noop(pos) | Stmt::Continue(pos) | Stmt::Break(pos) => *pos = new_pos, + Stmt::Let(x) => (x.0).1 = new_pos, + Stmt::Const(x) => (x.0).1 = new_pos, + Stmt::ReturnWithVal(x) => (x.0).1 = new_pos, + Stmt::Block(x) => x.1 = new_pos, + Stmt::IfThenElse(x) => x.3 = new_pos, + Stmt::Expr(x) => { + x.set_position(new_pos); + } + Stmt::While(x) => x.2 = new_pos, + Stmt::Loop(x) => x.1 = new_pos, + Stmt::For(x) => x.3 = new_pos, + + #[cfg(not(feature = "no_module"))] + Stmt::Import(x) => x.2 = new_pos, + #[cfg(not(feature = "no_module"))] + Stmt::Export(x) => x.1 = new_pos, + } + + self + } + /// Is this statement self-terminated (i.e. no need for a semicolon terminator)? pub fn is_self_terminated(&self) -> bool { match self { @@ -602,7 +631,7 @@ impl Stmt { } Stmt::IfThenElse(x) => x.1.is_pure(), Stmt::While(x) => x.0.is_pure() && x.1.is_pure(), - Stmt::Loop(x) => x.is_pure(), + Stmt::Loop(x) => x.0.is_pure(), Stmt::For(x) => x.1.is_pure() && x.2.is_pure(), Stmt::Let(_) | Stmt::Const(_) => false, Stmt::Block(x) => x.0.iter().all(Stmt::is_pure), @@ -832,11 +861,10 @@ impl Expr { } /// Override the `Position` of the expression. - pub(crate) fn set_position(mut self, new_pos: Position) -> Self { - match &mut self { - Self::Expr(ref mut x) => { - let expr = mem::take(x); - *x = Box::new(expr.set_position(new_pos)); + pub fn set_position(&mut self, new_pos: Position) -> &mut Self { + match self { + Self::Expr(x) => { + x.set_position(new_pos); } #[cfg(not(feature = "no_float"))] @@ -2314,7 +2342,7 @@ fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { match input.peek().unwrap() { (Token::Equals, pos) => { - return Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) + Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(*pos)) } (Token::PlusAssign, pos) | (Token::MinusAssign, pos) @@ -2326,12 +2354,10 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { | (Token::PowerOfAssign, pos) | (Token::AndAssign, pos) | (Token::OrAssign, pos) - | (Token::XOrAssign, pos) => { - return Err(PERR::BadInput( - "Expecting a boolean expression, not an assignment".to_string(), - ) - .into_err(*pos)) - } + | (Token::XOrAssign, pos) => Err(PERR::BadInput( + "Expecting a boolean expression, not an assignment".to_string(), + ) + .into_err(*pos)), _ => Ok(()), } @@ -2345,7 +2371,8 @@ fn parse_if( mut settings: ParseSettings, ) -> Result { // if ... - settings.pos = eat_token(input, Token::If); + let token_pos = eat_token(input, Token::If); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2369,7 +2396,9 @@ fn parse_if( None }; - Ok(Stmt::IfThenElse(Box::new((guard, if_body, else_body)))) + Ok(Stmt::IfThenElse(Box::new(( + guard, if_body, else_body, token_pos, + )))) } /// Parse a while loop. @@ -2380,7 +2409,8 @@ fn parse_while( mut settings: ParseSettings, ) -> Result { // while ... - settings.pos = eat_token(input, Token::While); + let token_pos = eat_token(input, Token::While); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2393,7 +2423,7 @@ fn parse_while( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::While(Box::new((guard, body)))) + Ok(Stmt::While(Box::new((guard, body, token_pos)))) } /// Parse a loop statement. @@ -2404,7 +2434,8 @@ fn parse_loop( mut settings: ParseSettings, ) -> Result { // loop ... - settings.pos = eat_token(input, Token::Loop); + let token_pos = eat_token(input, Token::Loop); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2413,7 +2444,7 @@ fn parse_loop( settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; - Ok(Stmt::Loop(Box::new(body))) + Ok(Stmt::Loop(Box::new((body, token_pos)))) } /// Parse a for loop. @@ -2424,7 +2455,8 @@ fn parse_for( mut settings: ParseSettings, ) -> Result { // for ... - settings.pos = eat_token(input, Token::For); + let token_pos = eat_token(input, Token::For); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2467,7 +2499,7 @@ fn parse_for( state.stack.truncate(prev_stack_len); - Ok(Stmt::For(Box::new((name, expr, body)))) + Ok(Stmt::For(Box::new((name, expr, body, token_pos)))) } /// Parse a variable definition statement. @@ -2479,7 +2511,8 @@ fn parse_let( mut settings: ParseSettings, ) -> Result { // let/const... (specified in `var_type`) - settings.pos = input.next().unwrap().1; + let token_pos = input.next().unwrap().1; + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2503,12 +2536,16 @@ fn parse_let( // let name = expr ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(((name, pos), Some(init_value))))) + Ok(Stmt::Let(Box::new(( + (name, pos), + Some(init_value), + token_pos, + )))) } // const name = { expr:constant } ScopeEntryType::Constant if init_value.is_constant() => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(((name, pos), init_value)))) + Ok(Stmt::Const(Box::new(((name, pos), init_value, token_pos)))) } // const name = expr: error ScopeEntryType::Constant => { @@ -2520,11 +2557,15 @@ fn parse_let( match var_type { ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new(((name, pos), None)))) + Ok(Stmt::Let(Box::new(((name, pos), None, token_pos)))) } ScopeEntryType::Constant => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new(((name, pos), Expr::Unit(pos))))) + Ok(Stmt::Const(Box::new(( + (name, pos), + Expr::Unit(pos), + token_pos, + )))) } } } @@ -2539,7 +2580,8 @@ fn parse_import( mut settings: ParseSettings, ) -> Result { // import ... - settings.pos = eat_token(input, Token::Import); + let token_pos = eat_token(input, Token::Import); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -2569,7 +2611,12 @@ fn parse_import( }; state.modules.push(name.clone()); - Ok(Stmt::Import(Box::new((expr, (name, settings.pos))))) + + Ok(Stmt::Import(Box::new(( + expr, + (name, settings.pos), + token_pos, + )))) } /// Parse an export statement. @@ -2580,7 +2627,8 @@ fn parse_export( _lib: &mut FunctionsLib, mut settings: ParseSettings, ) -> Result { - settings.pos = eat_token(input, Token::Export); + let token_pos = eat_token(input, Token::Export); + settings.pos = token_pos; #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(_state.max_expr_depth)?; @@ -2640,7 +2688,7 @@ fn parse_export( }) .map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?; - Ok(Stmt::Export(Box::new(exports))) + Ok(Stmt::Export(Box::new((exports, token_pos)))) } /// Parse a statement block. @@ -2827,22 +2875,32 @@ fn parse_stmt( Token::Continue | Token::Break => Err(PERR::LoopBreak.into_err(settings.pos)), Token::Return | Token::Throw => { - let return_type = match input.next().unwrap() { - (Token::Return, _) => ReturnType::Return, - (Token::Throw, _) => ReturnType::Exception, - _ => unreachable!(), - }; + let (return_type, token_pos) = input + .next() + .map(|(token, pos)| { + ( + match token { + Token::Return => ReturnType::Return, + Token::Throw => ReturnType::Exception, + _ => unreachable!(), + }, + pos, + ) + }) + .unwrap(); match input.peek().unwrap() { // `return`/`throw` at (Token::EOF, pos) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, *pos), None, + token_pos, ))))), // `return;` or `throw;` (Token::SemiColon, _) => Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, settings.pos), None, + token_pos, ))))), // `return` or `throw` with expression (_, _) => { @@ -2852,6 +2910,7 @@ fn parse_stmt( Ok(Some(Stmt::ReturnWithVal(Box::new(( (return_type, pos), Some(expr), + token_pos, ))))) } }