Add continue statement.

This commit is contained in:
Stephen Chung 2020-04-01 16:22:18 +08:00
parent 9aff10aca4
commit 4ea2fb88ae
6 changed files with 69 additions and 37 deletions

View File

@ -1242,9 +1242,10 @@ x == ();
let x = 10; let x = 10;
while x > 0 { while x > 0 {
x = x - 1;
if x < 6 { continue; } // skip to the next iteration
print(x); print(x);
if x == 5 { break; } // break out of while loop if x == 5 { break; } // break out of while loop
x = x - 1;
} }
``` ```
@ -1255,8 +1256,9 @@ Infinite `loop`
let x = 10; let x = 10;
loop { loop {
print(x);
x = x - 1; x = x - 1;
if x > 5 { continue; } // skip to the next iteration
print(x);
if x == 0 { break; } // break out of loop if x == 0 { break; } // break out of loop
} }
``` ```
@ -1271,14 +1273,16 @@ let array = [1, 3, 5, 7, 9, 42];
// Iterate through array // Iterate through array
for x in array { for x in array {
if x > 10 { continue; } // skip to the next iteration
print(x); 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 // The 'range' function allows iterating from first to last-1
for x in range(0, 50) { for x in range(0, 50) {
if x > 10 { continue; } // skip to the next iteration
print(x); 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: "" 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. with the exception text captured by the first parameter.
```rust ```rust
@ -1354,7 +1358,8 @@ print(add2(42)); // prints 44
### No access to external scope ### 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 ```rust
let x = 42; let x = 42;
@ -1390,7 +1395,7 @@ fn add(x, y) {
// The following will not compile // The following will not compile
fn do_addition(x) { 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 n + y
} }

View File

@ -1349,19 +1349,12 @@ impl Engine<'_> {
// While loop // While loop
Stmt::While(guard, body) => loop { Stmt::While(guard, body) => loop {
match self.eval_expr(scope, guard, level)?.downcast::<bool>() { match self.eval_expr(scope, guard, level)?.downcast::<bool>() {
Ok(guard_val) => { Ok(guard_val) if *guard_val => match self.eval_stmt(scope, body, level) {
if *guard_val { Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
match self.eval_stmt(scope, body, level) { Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
Ok(_) => (), Err(x) => return Err(x),
Err(EvalAltResult::ErrorLoopBreak(_)) => { },
return Ok(().into_dynamic()) Ok(_) => return Ok(().into_dynamic()),
}
Err(x) => return Err(x),
}
} else {
return Ok(().into_dynamic());
}
}
Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())), Err(_) => return Err(EvalAltResult::ErrorLogicGuard(guard.position())),
} }
}, },
@ -1369,8 +1362,8 @@ impl Engine<'_> {
// Loop statement // Loop statement
Stmt::Loop(body) => loop { Stmt::Loop(body) => loop {
match self.eval_stmt(scope, body, level) { match self.eval_stmt(scope, body, level) {
Ok(_) => (), Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(true, _)) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
} }
}, },
@ -1393,8 +1386,8 @@ impl Engine<'_> {
*scope.get_mut(entry) = a; *scope.get_mut(entry) = a;
match self.eval_stmt(scope, body, level) { match self.eval_stmt(scope, body, level) {
Ok(_) => (), Ok(_) | Err(EvalAltResult::ErrorLoopBreak(false, _)) => (),
Err(EvalAltResult::ErrorLoopBreak(_)) => break, Err(EvalAltResult::ErrorLoopBreak(true, _)) => break,
Err(x) => return Err(x), Err(x) => return Err(x),
} }
} }
@ -1406,8 +1399,11 @@ impl Engine<'_> {
} }
} }
// Continue statement
Stmt::Continue(pos) => Err(EvalAltResult::ErrorLoopBreak(false, *pos)),
// Break statement // Break statement
Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)), Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(true, *pos)),
// Empty return // Empty return
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {

View File

@ -278,6 +278,8 @@ pub enum Stmt {
Block(Vec<Stmt>, Position), Block(Vec<Stmt>, Position),
/// { stmt } /// { stmt }
Expr(Box<Expr>), Expr(Box<Expr>),
/// continue
Continue(Position),
/// break /// break
Break(Position), Break(Position),
/// `return`/`throw` /// `return`/`throw`
@ -292,6 +294,7 @@ impl Stmt {
| Stmt::Let(_, _, pos) | Stmt::Let(_, _, pos)
| Stmt::Const(_, _, pos) | Stmt::Const(_, _, pos)
| Stmt::Block(_, pos) | Stmt::Block(_, pos)
| Stmt::Continue(pos)
| Stmt::Break(pos) | Stmt::Break(pos)
| Stmt::ReturnWithVal(_, _, pos) => *pos, | Stmt::ReturnWithVal(_, _, pos) => *pos,
Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), Stmt::IfThenElse(expr, _, _) | Stmt::Expr(expr) => expr.position(),
@ -314,6 +317,7 @@ impl Stmt {
Stmt::Let(_, _, _) Stmt::Let(_, _, _)
| Stmt::Const(_, _, _) | Stmt::Const(_, _, _)
| Stmt::Expr(_) | Stmt::Expr(_)
| Stmt::Continue(_)
| Stmt::Break(_) | Stmt::Break(_)
| Stmt::ReturnWithVal(_, _, _) => false, | Stmt::ReturnWithVal(_, _, _) => false,
} }
@ -334,7 +338,7 @@ impl Stmt {
Stmt::For(_, range, block) => range.is_pure() && block.is_pure(), Stmt::For(_, range, block) => range.is_pure() && block.is_pure(),
Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false, Stmt::Let(_, _, _) | Stmt::Const(_, _, _) => false,
Stmt::Block(statements, _) => statements.iter().all(Stmt::is_pure), 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, And,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Fn, Fn,
Continue,
Break, Break,
Return, Return,
Throw, Throw,
@ -653,6 +658,7 @@ impl Token {
And => "&&", And => "&&",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Fn => "fn", Fn => "fn",
Continue => "continue",
Break => "break", Break => "break",
Return => "return", Return => "return",
Throw => "throw", Throw => "throw",
@ -1100,6 +1106,7 @@ impl<'a> TokenIterator<'a> {
"else" => Token::Else, "else" => Token::Else,
"while" => Token::While, "while" => Token::While,
"loop" => Token::Loop, "loop" => Token::Loop,
"continue" => Token::Continue,
"break" => Token::Break, "break" => Token::Break,
"return" => Token::Return, "return" => Token::Return,
"throw" => Token::Throw, "throw" => Token::Throw,
@ -2419,6 +2426,8 @@ fn parse_stmt<'a>(
// Semicolon - empty statement // Semicolon - empty statement
(Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)), (Token::SemiColon, pos) => Ok(Stmt::Noop(*pos)),
(Token::LeftBrace, _) => parse_block(input, breakable, allow_stmt_expr),
// fn ... // fn ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
(Token::Fn, pos) => Err(PERR::WrongFnDefinition.into_err(*pos)), (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::While, _) => parse_while(input, allow_stmt_expr),
(Token::Loop, _) => parse_loop(input, allow_stmt_expr), (Token::Loop, _) => parse_loop(input, allow_stmt_expr),
(Token::For, _) => parse_for(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 => { (Token::Break, pos) if breakable => {
let pos = *pos; let pos = *pos;
input.next(); input.next();
Ok(Stmt::Break(pos)) 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) => { (token @ Token::Return, pos) | (token @ Token::Throw, pos) => {
let return_type = match token { let return_type = match token {
Token::Return => ReturnType::Return, 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::Let, _) => parse_let(input, ScopeEntryType::Normal, allow_stmt_expr),
(Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr), (Token::Const, _) => parse_let(input, ScopeEntryType::Constant, allow_stmt_expr),
_ => parse_expr_stmt(input, allow_stmt_expr), _ => parse_expr_stmt(input, allow_stmt_expr),
} }
} }

View File

@ -70,7 +70,9 @@ pub enum EvalAltResult {
ErrorRuntime(String, Position), ErrorRuntime(String, Position),
/// Breaking out of loops - not an error if within a loop. /// 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. /// Not an error: Value returned from a script via the `return` keyword.
/// Wrapped value is the result value. /// Wrapped value is the result value.
Return(Dynamic, Position), Return(Dynamic, Position),
@ -118,7 +120,8 @@ impl EvalAltResult {
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorStackOverflow(_) => "Stack overflow", Self::ErrorStackOverflow(_) => "Stack overflow",
Self::ErrorRuntime(_, _) => "Runtime error", 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", 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::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos),
Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", 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::Return(_, pos) => write!(f, "{} ({})", desc, pos),
Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!( Self::ErrorFunctionArgsMismatch(fn_name, 0, n, pos) => write!(
@ -255,7 +258,7 @@ impl EvalAltResult {
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorStackOverflow(pos) | Self::ErrorStackOverflow(pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(pos) | Self::ErrorLoopBreak(_, pos)
| Self::Return(_, pos) => *pos, | Self::Return(_, pos) => *pos,
} }
} }
@ -287,7 +290,7 @@ impl EvalAltResult {
| Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorArithmetic(_, ref mut pos)
| Self::ErrorStackOverflow(ref mut pos) | Self::ErrorStackOverflow(ref mut pos)
| Self::ErrorRuntime(_, 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, | Self::Return(_, ref mut pos) => *pos = new_position,
} }

View File

@ -12,8 +12,9 @@ fn test_loop() -> Result<(), EvalAltResult> {
loop { loop {
if i < 10 { if i < 10 {
i += 1;
if x > 20 { continue; }
x = x + i; x = x + i;
i = i + 1;
} else { } else {
break; break;
} }
@ -22,7 +23,7 @@ fn test_loop() -> Result<(), EvalAltResult> {
return x; return x;
" "
)?, )?,
45 21
); );
Ok(()) Ok(())

View File

@ -6,8 +6,18 @@ fn test_while() -> Result<(), EvalAltResult> {
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
"let x = 0; while x < 10 { x = x + 1; if x > 5 { \ r"
break } } x", let x = 0;
while x < 10 {
x = x + 1;
if x > 5 { break; }
if x > 3 { continue; }
x = x + 3;
}
x
",
)?, )?,
6 6
); );