diff --git a/README.md b/README.md index 969e727a..41776cd5 100644 --- a/README.md +++ b/README.md @@ -1242,9 +1242,10 @@ x == (); let x = 10; while x > 0 { + x = x - 1; + if x < 6 { continue; } // skip to the next iteration print(x); if x == 5 { break; } // break out of while loop - x = x - 1; } ``` @@ -1255,8 +1256,9 @@ Infinite `loop` let x = 10; loop { - print(x); x = x - 1; + if x > 5 { continue; } // skip to the next iteration + print(x); if x == 0 { break; } // break out of loop } ``` @@ -1271,14 +1273,16 @@ let array = [1, 3, 5, 7, 9, 42]; // Iterate through array for x in array { + if x > 10 { continue; } // skip to the next iteration print(x); - if x == 42 { break; } + if x == 42 { break; } // break out of for loop } // The 'range' function allows iterating from first to last-1 for x in range(0, 50) { + if x > 10 { continue; } // skip to the next iteration print(x); - if x == 42 { break; } + if x == 42 { break; } // break out of for loop } ``` @@ -1305,7 +1309,7 @@ if some_bad_condition_has_happened { throw; // defaults to empty exception text: "" ``` -Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(`_reason_`, `_position_`))` +Exceptions thrown via `throw` in the script can be captured by matching `Err(EvalAltResult::ErrorRuntime(` _reason_ `,` _position_ `))` with the exception text captured by the first parameter. ```rust @@ -1354,7 +1358,8 @@ print(add2(42)); // prints 44 ### No access to external scope -Functions can only access their parameters. They cannot access external variables (even _global_ variables). +Functions are not _closures_. They do not capture the calling environment and can only access their own parameters. +They cannot access variables external to the function itself. ```rust let x = 42; @@ -1390,7 +1395,7 @@ fn add(x, y) { // The following will not compile fn do_addition(x) { - fn add_y(n) { // functions cannot be defined inside another function + fn add_y(n) { // <- syntax error: functions cannot be defined inside another function n + y } diff --git a/src/engine.rs b/src/engine.rs index b20cfabd..2d3e955d 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1349,19 +1349,12 @@ impl Engine<'_> { // While loop Stmt::While(guard, body) => loop { match self.eval_expr(scope, guard, level)?.downcast::() { - Ok(guard_val) => { - if *guard_val { - match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => { - return Ok(().into_dynamic()) - } - Err(x) => return Err(x), - } - } else { - return Ok(().into_dynamic()); - } - } + Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) { + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), + Err(x) => return Err(x), + }, + Ok(_) => return Ok(().into_dynamic()), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), } }, @@ -1369,8 +1362,8 @@ impl Engine<'_> { // Loop statement Stmt::Loop(body) => loop { match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()), Err(x) => return Err(x), } }, @@ -1393,8 +1386,8 @@ impl Engine<'_> { *scope.get_mut(entry) = a; match self.eval_stmt(scope, body, level) { - Ok(_) => (), - Err(EvalAltResult::ErrorLoopBreak(_)) => break, + Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (), + Err(EvalAltResult::ErrorLoopBreak(true, _)) => break, Err(x) => return Err(x), } } @@ -1406,8 +1399,11 @@ impl Engine<'_> { } } + // Continue statement + Stmt::Continue(pos) => Err(EvalAltResult::ErrorLoopBreak(false, *pos)), + // Break statement - Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)), + Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(true, *pos)), // Empty return Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { diff --git a/src/parser.rs b/src/parser.rs index f44660c4..c3d16f2e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -278,6 +278,8 @@ pub enum Stmt { Block(Vec, Position), /// { stmt } Expr(Box), + /// continue + Continue(Position), /// break Break(Position), /// `return`/`throw` @@ -292,6 +294,7 @@ impl Stmt { | Stmt::Let(_, _, pos) | Stmt::Const(_, _, pos) | Stmt::Block(_, pos) + | Stmt::Continue(pos) | Stmt::Break(pos) | Stmt::ReturnWithVal(_, _, pos) => *pos, Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), @@ -314,6 +317,7 @@ impl Stmt { Stmt::Let(_, _, _) | Stmt::Const(_, _, _) | Stmt::Expr(_) + | Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, } @@ -334,7 +338,7 @@ impl Stmt { 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, + Stmt::Continue(_) | Stmt::Break(_) | Stmt::ReturnWithVal(_, _, _) => false, } } } @@ -579,6 +583,7 @@ pub enum Token { And, #[cfg(not(feature = "no_function"))] Fn, + Continue, Break, Return, Throw, @@ -653,6 +658,7 @@ impl Token { And => "&&", #[cfg(not(feature = "no_function"))] Fn => "fn", + Continue => "continue", Break => "break", Return => "return", Throw => "throw", @@ -1100,6 +1106,7 @@ impl<'a> TokenIterator<'a> { "else" => Token::Else, "while" => Token::While, "loop" => Token::Loop, + "continue" => Token::Continue, "break" => Token::Break, "return" => Token::Return, "throw" => Token::Throw, @@ -2419,6 +2426,8 @@ fn parse_stmt<'a>( // Semicolon - empty statement (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), + (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), + // fn ... #[cfg(not(feature = "no_function"))] (Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), @@ -2427,12 +2436,19 @@ fn parse_stmt<'a>( (Token::While, _) => parse_while(input, allow_stmt_expr), (Token::Loop, _) => parse_loop(input, allow_stmt_expr), (Token::For, _) => parse_for(input, allow_stmt_expr), + + (Token::Continue, pos) if breakable => { + let pos = *pos; + input.next(); + Ok(Stmt::Continue(pos)) + } (Token::Break, pos) if breakable => { let pos = *pos; input.next(); Ok(Stmt::Break(pos)) } - (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), + (Token::Continue, pos) | (Token::Break, pos) => Err(PERR::LoopBreak.into_err(*pos)), + (token @ Token::Return, pos) | (token @ Token::Throw, pos) => { let return_type = match token { Token::Return => ReturnType::Return, @@ -2456,9 +2472,10 @@ fn parse_stmt<'a>( } } } - (Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr), + (Token::Let, _) => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr), (Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), + _ => parse_expr_stmt(input, allow_stmt_expr), } } diff --git a/src/result.rs b/src/result.rs index 0c882609..474ae8b9 100644 --- a/src/result.rs +++ b/src/result.rs @@ -70,7 +70,9 @@ pub enum EvalAltResult { ErrorRuntime(String, Position), /// Breaking out of loops - not an error if within a loop. - ErrorLoopBreak(Position), + /// The wrapped value, if true, means breaking clean out of the loop (i.e. a `break` statement). + /// The wrapped value, if false, means breaking the current context (i.e. a `continue` statement). + ErrorLoopBreak(bool, Position), /// Not an error: Value returned from a script via the `return` keyword. /// Wrapped value is the result value. Return(Dynamic, Position), @@ -118,7 +120,8 @@ impl EvalAltResult { Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorRuntime(_, _) => "Runtime error", - Self::ErrorLoopBreak(_) => "Break statement not inside a loop", + Self::ErrorLoopBreak(true, _) => "Break statement not inside a loop", + Self::ErrorLoopBreak(false, _) => "Continue statement not inside a loop", Self::Return(_, _) => "[Not Error] Function returns value", } } @@ -160,7 +163,7 @@ impl fmt::Display for EvalAltResult { Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos), - Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!( @@ -255,7 +258,7 @@ impl EvalAltResult { | Self::ErrorArithmetic(_, pos) | Self::ErrorStackOverflow(pos) | Self::ErrorRuntime(_, pos) - | Self::ErrorLoopBreak(pos) + | Self::ErrorLoopBreak(_, pos) | Self::Return(_, pos) => *pos, } } @@ -287,7 +290,7 @@ impl EvalAltResult { | Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorStackOverflow(ref mut pos) | Self::ErrorRuntime(_, ref mut pos) - | Self::ErrorLoopBreak(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 651cbe32..bf2c1a72 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -12,8 +12,9 @@ fn test_loop() -> Result<(), EvalAltResult> { loop { if i < 10 { + i += 1; + if x > 20 { continue; } x = x + i; - i = i + 1; } else { break; } @@ -22,7 +23,7 @@ fn test_loop() -> Result<(), EvalAltResult> { return x; " )?, - 45 + 21 ); Ok(()) diff --git a/tests/while_loop.rs b/tests/while_loop.rs index fa76ea17..18d5a03b 100644 --- a/tests/while_loop.rs +++ b/tests/while_loop.rs @@ -6,8 +6,18 @@ fn test_while() -> Result<(), EvalAltResult> { assert_eq!( engine.eval::( - "let x = 0; while x < 10 { x = x + 1; if x > 5 { \ - break } } x", + r" + let x = 0; + + while x < 10 { + x = x + 1; + if x > 5 { break; } + if x > 3 { continue; } + x = x + 3; + } + + x + ", )?, 6 );