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;
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
}

View File

@ -1349,19 +1349,12 @@ impl Engine<'_> {
// While loop
Stmt::While(guard, body) => loop {
match self.eval_expr(scope, guard, level)?.downcast::<bool>() {
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) => {

View File

@ -278,6 +278,8 @@ pub enum Stmt {
Block(Vec<Stmt>, Position),
/// { stmt }
Expr(Box<Expr>),
/// 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),
}
}

View File

@ -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,
}

View File

@ -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(())

View File

@ -6,8 +6,18 @@ fn test_while() -> Result<(), EvalAltResult> {
assert_eq!(
engine.eval::<INT>(
"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
);