diff --git a/examples/repl.rs b/examples/repl.rs index 1187d1c9..953ee2a9 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -36,10 +36,18 @@ fn print_error(input: &str, err: EvalAltResult) { ); println!("{}", lines[p.line().unwrap() - 1]); + + let err_text = match err { + EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { + format!("Runtime error: {}", err) + } + _ => err.to_string(), + }; + println!( "{}^ {}", padding(" ", p.position().unwrap() - 1), - err.to_string().replace(&pos_text, "") + err_text.replace(&pos_text, "") ); } } @@ -86,7 +94,12 @@ fn main() { .map_err(EvalAltResult::ErrorParsing) .and_then(|r| { ast = Some(r); - engine.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) + engine + .consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) + .or_else(|err| match err { + EvalAltResult::Return(_, _) => Ok(()), + err => Err(err), + }) }) { println!(""); diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index 4da91feb..2b9b2077 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -31,7 +31,13 @@ fn eprint_error(input: &str, err: EvalAltResult) { // EOF let line = lines.len() - 1; let pos = lines[line - 1].len(); - eprint_line(&lines, line, pos, &err.to_string()); + let err_text = match err { + EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { + format!("Runtime error: {}", err) + } + _ => err.to_string(), + }; + eprint_line(&lines, line, pos, &err_text); } p if p.is_none() => { // No position diff --git a/src/engine.rs b/src/engine.rs index 9362dd30..e1290e07 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1068,7 +1068,9 @@ impl Engine<'_> { if *guard_val { match self.eval_stmt(scope, body) { Ok(_) => (), - Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), + Err(EvalAltResult::ErrorLoopBreak(_)) => { + return Ok(().into_dynamic()) + } Err(x) => return Err(x), } } else { @@ -1083,7 +1085,7 @@ impl Engine<'_> { Stmt::Loop(body) => loop { match self.eval_stmt(scope, body) { Ok(_) => (), - Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), + Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), Err(x) => return Err(x), } }, @@ -1102,7 +1104,7 @@ impl Engine<'_> { match self.eval_stmt(scope, body) { Ok(_) => (), - Err(EvalAltResult::LoopBreak) => break, + Err(EvalAltResult::ErrorLoopBreak(_)) => break, Err(x) => return Err(x), } } @@ -1114,7 +1116,7 @@ impl Engine<'_> { } // Break statement - Stmt::Break(_) => Err(EvalAltResult::LoopBreak), + Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)), // Empty return Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { diff --git a/src/error.rs b/src/error.rs index c04e2c32..59816e96 100644 --- a/src/error.rs +++ b/src/error.rs @@ -82,12 +82,17 @@ pub enum ParseErrorType { /// A function definition is missing the parameters list. Wrapped value is the function name. #[cfg(not(feature = "no_function"))] FnMissingParams(String), + /// A function definition is missing the body. Wrapped value is the function name. + #[cfg(not(feature = "no_function"))] + FnMissingBody(String), /// Assignment to an inappropriate LHS (left-hand-side) expression. AssignmentToInvalidLHS, /// Assignment to a copy of a value. AssignmentToCopy, /// Assignment to an a constant variable. AssignmentToConstant(String), + /// Break statement not inside a loop. + LoopBreak, } /// Error when parsing a script. @@ -133,10 +138,13 @@ impl ParseError { #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", #[cfg(not(feature = "no_function"))] + ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration", + #[cfg(not(feature = "no_function"))] ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value", - ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable." + ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable.", + ParseErrorType::LoopBreak => "Break statement should only be used inside a loop" } } } @@ -164,6 +172,11 @@ impl fmt::Display for ParseError { write!(f, "Expecting parameters for function '{}'", s)? } + #[cfg(not(feature = "no_function"))] + ParseErrorType::FnMissingBody(ref s) => { + write!(f, "Expecting body statement block for function '{}'", s)? + } + ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => { write!(f, "{} for {}", self.desc(), s)? } diff --git a/src/optimize.rs b/src/optimize.rs index 0c7d2c5b..227110ee 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -4,7 +4,7 @@ use crate::any::{Any, Dynamic}; use crate::engine::{ Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST}; use crate::scope::{Scope, ScopeEntry, VariableType}; use crate::stdlib::{ @@ -121,18 +121,40 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - Stmt::Noop(pos) } Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))), - expr => Stmt::While( - Box::new(optimize_expr(expr, state)), - Box::new(optimize_stmt(*stmt, state, false)), - ), + expr => match optimize_stmt(*stmt, state, false) { + Stmt::Break(pos) => { + // Only a single break statement - turn into running the guard expression once + state.set_dirty(); + let mut statements = vec![Stmt::Expr(Box::new(optimize_expr(expr, state)))]; + if preserve_result { + statements.push(Stmt::Noop(pos)) + } + Stmt::Block(statements, pos) + } + stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)), + }, + }, + Stmt::Loop(stmt) => match optimize_stmt(*stmt, state, false) { + Stmt::Break(pos) => { + // Only a single break statement + state.set_dirty(); + Stmt::Noop(pos) + } + stmt => Stmt::Loop(Box::new(stmt)), }, - - Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))), Stmt::For(id, expr, stmt) => Stmt::For( id, Box::new(optimize_expr(*expr, state)), - Box::new(optimize_stmt(*stmt, state, false)), + Box::new(match optimize_stmt(*stmt, state, false) { + Stmt::Break(pos) => { + // Only a single break statement + state.set_dirty(); + Stmt::Noop(pos) + } + stmt => stmt, + }), ), + Stmt::Let(id, Some(expr), pos) => { Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos) } @@ -153,15 +175,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - optimize_stmt(stmt, state, preserve_result) // Optimize the statement } }) - .enumerate() - .filter(|(i, stmt)| stmt.is_op() || (preserve_result && *i == orig_len - 1)) // Remove no-op's but leave the last one if we need the result - .map(|(_, stmt)| stmt) .collect(); // Remove all raw expression statements that are pure except for the very last statement let last_stmt = if preserve_result { result.pop() } else { None }; - result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure())); + result.retain(|stmt| !stmt.is_pure()); if let Some(stmt) = last_stmt { result.push(stmt); @@ -197,6 +216,24 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) - .collect(); } + // Remove everything following the the first return/throw + let mut dead_code = false; + + result.retain(|stmt| { + if dead_code { + return false; + } + + match stmt { + Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => { + dead_code = true; + } + _ => (), + } + + true + }); + if orig_len != result.len() { state.set_dirty(); } @@ -504,7 +541,15 @@ pub fn optimize_ast( OptimizationLevel::Simple | OptimizationLevel::Full => { let pos = fn_def.body.position(); let mut body = optimize(vec![fn_def.body], None, &Scope::new()); - fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos)); + fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { + Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => { + Stmt::Expr(val) + } + Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { + Stmt::Expr(Box::new(Expr::Unit(pos))) + } + stmt => stmt, + }; } } Arc::new(fn_def) diff --git a/src/parser.rs b/src/parser.rs index bdfa1f6c..702f0fed 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -201,6 +201,19 @@ pub enum Stmt { } impl Stmt { + pub fn position(&self) -> Position { + match self { + Stmt::Noop(pos) + | Stmt::Let(_, _, pos) + | Stmt::Const(_, _, pos) + | Stmt::Block(_, pos) + | Stmt::Break(pos) + | Stmt::ReturnWithVal(_, _, pos) => *pos, + Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), + Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), + } + } + pub fn is_noop(&self) -> bool { matches!(self, Stmt::Noop(_)) } @@ -230,16 +243,21 @@ impl Stmt { } } - pub fn position(&self) -> Position { + pub fn is_pure(&self) -> bool { match self { - Stmt::Noop(pos) - | Stmt::Let(_, _, pos) - | Stmt::Const(_, _, pos) - | Stmt::Block(_, pos) - | Stmt::Break(pos) - | Stmt::ReturnWithVal(_, _, pos) => *pos, - Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), - Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), + Stmt::Noop(_) => true, + Stmt::Expr(expr) => expr.is_pure(), + Stmt::IfElse(guard, if_block, Some(else_block)) => { + guard.is_pure() && if_block.is_pure() && else_block.is_pure() + } + Stmt::IfElse(guard, block, None) | Stmt::While(guard, block) => { + guard.is_pure() && block.is_pure() + } + Stmt::Loop(block) => block.is_pure(), + Stmt::For(_, range, block) => range.is_pure() && block.is_pure(), + Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, + Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), + Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, } } } @@ -353,6 +371,8 @@ impl Expr { Expr::And(x, y) | Expr::Or(x, y) => x.is_pure() && y.is_pure(), + Expr::Stmt(stmt, _) => stmt.is_pure(), + expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)), } } @@ -1457,7 +1477,7 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result { let pos = *pos; - return parse_block(input).map(|block| Expr::Stmt(Box::new(block), pos)); + return parse_block(input, false).map(|block| Expr::Stmt(Box::new(block), pos)); } _ => (), } @@ -1467,38 +1487,39 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Ok(Expr::FloatConstant(x, pos)), + let mut root_expr = match token + .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? + { + #[cfg(not(feature = "no_float"))] + (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), - (Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)), - (Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)), - (Token::StringConst(s), pos) => { - can_be_indexed = true; - Ok(Expr::StringConstant(s, pos)) - } - (Token::Identifier(s), pos) => { - can_be_indexed = true; - parse_ident_expr(s, input, pos) - } - (Token::LeftParen, pos) => { - can_be_indexed = true; - parse_paren_expr(input, pos) - } - #[cfg(not(feature = "no_index"))] - (Token::LeftBracket, pos) => { - can_be_indexed = true; - parse_array_expr(input, pos) - } - (Token::True, pos) => Ok(Expr::True(pos)), - (Token::False, pos) => Ok(Expr::False(pos)), - (Token::LexError(le), pos) => Err(ParseError::new(PERR::BadInput(le.to_string()), pos)), - (token, pos) => Err(ParseError::new( - PERR::BadInput(format!("Unexpected '{}'", token.syntax())), - pos, - )), - }?; + (Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)), + (Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)), + (Token::StringConst(s), pos) => { + can_be_indexed = true; + Ok(Expr::StringConstant(s, pos)) + } + (Token::Identifier(s), pos) => { + can_be_indexed = true; + parse_ident_expr(s, input, pos) + } + (Token::LeftParen, pos) => { + can_be_indexed = true; + parse_paren_expr(input, pos) + } + #[cfg(not(feature = "no_index"))] + (Token::LeftBracket, pos) => { + can_be_indexed = true; + parse_array_expr(input, pos) + } + (Token::True, pos) => Ok(Expr::True(pos)), + (Token::False, pos) => Ok(Expr::False(pos)), + (Token::LexError(err), pos) => Err(ParseError::new(PERR::BadInput(err.to_string()), pos)), + (token, pos) => Err(ParseError::new( + PERR::BadInput(format!("Unexpected '{}'", token.syntax())), + pos, + )), + }?; if can_be_indexed { // Tail processing all possible indexing @@ -1814,19 +1835,22 @@ fn parse_expr<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { +fn parse_if<'a>( + input: &mut Peekable>, + breakable: bool, +) -> Result { input.next(); let guard = parse_expr(input)?; - let if_body = parse_block(input)?; + let if_body = parse_block(input, breakable)?; let else_body = if matches!(input.peek(), Some((Token::Else, _))) { input.next(); Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) { - parse_if(input)? + parse_if(input, breakable)? } else { - parse_block(input)? + parse_block(input, breakable)? })) } else { None @@ -1839,7 +1863,7 @@ fn parse_while<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { input.next(); - let body = parse_block(input)?; + let body = parse_block(input, true)?; Ok(Stmt::Loop(Box::new(body))) } @@ -1860,8 +1884,8 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result s, - (Token::LexError(s), pos) => { - return Err(ParseError::new(PERR::BadInput(s.to_string()), pos)) + (Token::LexError(err), pos) => { + return Err(ParseError::new(PERR::BadInput(err.to_string()), pos)) } (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), }; @@ -1875,7 +1899,7 @@ fn parse_for<'a>(input: &mut Peekable>) -> Result( .ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))? { (Token::Identifier(s), _) => s, - (Token::LexError(s), pos) => { - return Err(ParseError::new(PERR::BadInput(s.to_string()), pos)) + (Token::LexError(err), pos) => { + return Err(ParseError::new(PERR::BadInput(err.to_string()), pos)) } (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), }; @@ -1921,7 +1945,10 @@ fn parse_var<'a>( } } -fn parse_block<'a>(input: &mut Peekable>) -> Result { +fn parse_block<'a>( + input: &mut Peekable>, + breakable: bool, +) -> Result { let pos = match input .next() .ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))? @@ -1932,50 +1959,31 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result (), // empty block + while !matches!(input.peek(), Some((Token::RightBrace, _))) { + // Parse statements inside the block + let stmt = parse_stmt(input, breakable)?; - _ => { - while input.peek().is_some() { - // Parse statements inside the block - let stmt = parse_stmt(input)?; + // See if it needs a terminating semicolon + let need_semicolon = !stmt.is_self_terminated(); - // See if it needs a terminating semicolon - let need_semicolon = !stmt.is_self_terminated(); + statements.push(stmt); - statements.push(stmt); + match input.peek() { + None => break, - // End block with right brace - if let Some((Token::RightBrace, _)) = input.peek() { - break; - } + Some((Token::RightBrace, _)) => break, - match input.peek() { - Some((Token::SemiColon, _)) => { - input.next(); - } - Some((_, _)) if !need_semicolon => (), + Some((Token::SemiColon, _)) => { + input.next(); + } + Some((_, _)) if !need_semicolon => (), - Some((_, pos)) => { - // Semicolons are not optional between statements - return Err(ParseError::new( - PERR::MissingSemicolon("terminating a statement".into()), - *pos, - )); - } - - None => { - return Err(ParseError::new( - PERR::MissingRightBrace("end of block".into()), - Position::eof(), - )) - } - } + Some((_, pos)) => { + // Semicolons are not optional between statements + return Err(ParseError::new( + PERR::MissingSemicolon("terminating a statement".into()), + *pos, + )); } } } @@ -2001,7 +2009,10 @@ fn parse_expr_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { +fn parse_stmt<'a>( + input: &mut Peekable>, + breakable: bool, +) -> Result { match input .peek() .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? @@ -2009,15 +2020,16 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result return Err(ParseError::new(PERR::WrongFnDefinition, *pos)), - (Token::If, _) => parse_if(input), + (Token::If, _) => parse_if(input, breakable), (Token::While, _) => parse_while(input), (Token::Loop, _) => parse_loop(input), (Token::For, _) => parse_for(input), - (Token::Break, pos) => { + (Token::Break, pos) if breakable => { let pos = *pos; input.next(); Ok(Stmt::Break(pos)) } + (Token::Break, pos) => return Err(ParseError::new(PERR::LoopBreak, *pos)), (token @ Token::Return, _) | (token @ Token::Throw, _) => { let return_type = match token { Token::Return => ReturnType::Return, @@ -2038,12 +2050,15 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result { let pos = *pos; - let ret = parse_expr(input)?; - Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), return_type, pos)) + Ok(Stmt::ReturnWithVal( + Some(Box::new(parse_expr(input)?)), + return_type, + pos, + )) } } } - (Token::LeftBrace, _) => parse_block(input), + (Token::LeftBrace, _) => parse_block(input, breakable), (Token::Let, _) => parse_var(input, VariableType::Normal), (Token::Const, _) => parse_var(input, VariableType::Constant), _ => parse_expr_stmt(input), @@ -2137,7 +2152,11 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result parse_block(input, false)?, + Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingBody(name), *pos)), + None => return Err(ParseError::new(PERR::FnMissingBody(name), Position::eof())), + }; Ok(FnDef { name, @@ -2169,7 +2188,7 @@ fn parse_global_level<'a, 'e>( } } - let stmt = parse_stmt(input)?; + let stmt = parse_stmt(input, false)?; let need_semicolon = !stmt.is_self_terminated(); diff --git a/src/result.rs b/src/result.rs index 12eb9d78..75bd92a2 100644 --- a/src/result.rs +++ b/src/result.rs @@ -62,8 +62,8 @@ pub enum EvalAltResult { ErrorArithmetic(String, Position), /// Run-time error encountered. Wrapped value is the error message. ErrorRuntime(String, Position), - /// Internal use: Breaking out of loops. - LoopBreak, + /// Breaking out of loops - not an error if within a loop. + ErrorLoopBreak(Position), /// Not an error: Value returned from a script via the `return` keyword. /// Wrapped value is the result value. Return(Dynamic, Position), @@ -106,7 +106,7 @@ impl EvalAltResult { Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorRuntime(_, _) => "Runtime error", - Self::LoopBreak => "[Not Error] Breaks out of loop", + Self::ErrorLoopBreak(_) => "Break statement not inside a loop", Self::Return(_, _) => "[Not Error] Function returns value", } } @@ -134,7 +134,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorRuntime(s, pos) => { write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos) } - Self::LoopBreak => write!(f, "{}", desc), + Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), #[cfg(not(feature = "no_stdlib"))] Self::ErrorReadingScriptFile(path, err) => { @@ -211,7 +211,6 @@ impl EvalAltResult { match self { #[cfg(not(feature = "no_stdlib"))] Self::ErrorReadingScriptFile(_, _) => Position::none(), - Self::LoopBreak => Position::none(), Self::ErrorParsing(err) => err.position(), @@ -232,6 +231,7 @@ impl EvalAltResult { | Self::ErrorDotExpr(_, pos) | Self::ErrorArithmetic(_, pos) | Self::ErrorRuntime(_, pos) + | Self::ErrorLoopBreak(pos) | Self::Return(_, pos) => *pos, } } @@ -240,7 +240,6 @@ impl EvalAltResult { match self { #[cfg(not(feature = "no_stdlib"))] Self::ErrorReadingScriptFile(_, _) => (), - Self::LoopBreak => (), Self::ErrorParsing(ParseError(_, ref mut pos)) | Self::ErrorFunctionNotFound(_, ref mut pos) @@ -260,6 +259,7 @@ impl EvalAltResult { | Self::ErrorDotExpr(_, ref mut pos) | Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorRuntime(_, ref mut pos) + | Self::ErrorLoopBreak(ref mut pos) | Self::Return(_, ref mut pos) => *pos = new_position, } } diff --git a/tests/looping.rs b/tests/looping.rs index 4baa0aab..651cbe32 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -1,27 +1,29 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, INT}; #[test] fn test_loop() -> Result<(), EvalAltResult> { let mut engine = Engine::new(); - assert!(engine.eval::( - r" - let x = 0; - let i = 0; + assert_eq!( + engine.eval::( + r" + let x = 0; + let i = 0; - loop { - if i < 10 { - x = x + i; - i = i + 1; + loop { + if i < 10 { + x = x + i; + i = i + 1; + } else { + break; + } } - else { - break; - } - } - x == 45 + return x; " - )?); + )?, + 45 + ); Ok(()) } diff --git a/tests/throw.rs b/tests/throw.rs index 518f56d8..690d9c6e 100644 --- a/tests/throw.rs +++ b/tests/throw.rs @@ -9,6 +9,6 @@ fn test_throw() { EvalAltResult::ErrorRuntime(s, _) if s == "hello")); assert!(matches!( - engine.eval::<()>(r#"throw;"#).expect_err("expects error"), + engine.eval::<()>(r#"throw"#).expect_err("expects error"), EvalAltResult::ErrorRuntime(s, _) if s == "")); }