diff --git a/RELEASES.md b/RELEASES.md
index b4f1a290..4d5f3028 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -14,11 +14,13 @@ Breaking changes
----------------
* `Module::set_fn`, `Module::set_raw_fn` and `Module::set_fn_XXX_mut` all take an additional parameter of `FnNamespace`.
+* `unless` is now a reserved keyword.
New features
------------
* `switch` statement.
+* `do ... while` and `do ... until` statement.
* `Engine::register_module` to register a module as a sub-module in the global namespace.
* `set_exported_global_fn!` macro to register a plugin function and expose it to the global namespace.
* `Module::set_fn_XXX_mut` can expose a module function to the global namespace. This is convenient when registering an API for a custom type.
diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md
index 8bdce36f..73332147 100644
--- a/doc/src/SUMMARY.md
+++ b/doc/src/SUMMARY.md
@@ -79,6 +79,7 @@ The Rhai Scripting Language
9. [If Statement](language/if.md)
10. [Switch Expression](language/switch.md)
11. [While Loop](language/while.md)
+ 11. [Do Loop](language/do.md)
12. [Loop Statement](language/loop.md)
13. [For Loop](language/for.md)
14. [Return Values](language/return.md)
diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md
index ad8d3e26..0e542dcf 100644
--- a/doc/src/appendix/keywords.md
+++ b/doc/src/appendix/keywords.md
@@ -14,7 +14,9 @@ Keywords List
| `if` | if statement | | no | |
| `else` | else block of if statement | | no | |
| `switch` | matching | | no | |
-| `while` | while loop | | no | |
+| `do` | looping | | no | |
+| `while` | 1) while loop
2) condition for do loop | | no | |
+| `until` | do loop | | no | |
| `loop` | infinite loop | | no | |
| `for` | for loop | | no | |
| `in` | 1) containment test
2) part of for loop | | no | |
@@ -48,11 +50,11 @@ Reserved Keywords
| `var` | variable declaration |
| `static` | variable declaration |
| `shared` | share value |
-| `do` | looping |
| `each` | looping |
| `then` | control flow |
| `goto` | control flow |
| `exit` | control flow |
+| `unless` | control flow |
| `match` | matching |
| `case` | matching |
| `public` | function/field access |
diff --git a/doc/src/language/do.md b/doc/src/language/do.md
new file mode 100644
index 00000000..c1129944
--- /dev/null
+++ b/doc/src/language/do.md
@@ -0,0 +1,28 @@
+`do` Loop
+=========
+
+{{#include ../links.md}}
+
+`do` loops have two opposite variants: `do` ... `while` and `do` ... `until`.
+
+Like the `while` loop, `continue` can be used to skip to the next iteration, by-passing all following statements;
+`break` can be used to break out of the loop unconditionally.
+
+```rust
+let x = 10;
+
+do {
+ x -= 1;
+ if x < 6 { continue; } // skip to the next iteration
+ print(x);
+ if x == 5 { break; } // break out of do loop
+} while x > 0;
+
+
+do {
+ x -= 1;
+ if x < 6 { continue; } // skip to the next iteration
+ print(x);
+ if x == 5 { break; } // break out of do loop
+} until x == 0;
+```
diff --git a/src/ast.rs b/src/ast.rs
index c675ce67..5e71db1e 100644
--- a/src/ast.rs
+++ b/src/ast.rs
@@ -601,8 +601,8 @@ pub enum Stmt {
),
/// `while` expr `{` stmt `}`
While(Expr, Box, Position),
- /// `loop` `{` stmt `}`
- Loop(Box, Position),
+ /// `do` `{` stmt `}` `while`|`until` expr
+ Do(Box, Expr, bool, Position),
/// `for` id `in` expr `{` stmt `}`
For(Expr, Box<(String, Stmt)>, Position),
/// \[`export`\] `let` id `=` expr
@@ -660,7 +660,7 @@ impl Stmt {
| Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos)
- | Self::Loop(_, pos)
+ | Self::Do(_, _, _, pos)
| Self::For(_, _, pos)
| Self::Return((_, pos), _, _)
| Self::Let(_, _, _, pos)
@@ -689,7 +689,7 @@ impl Stmt {
| Self::If(_, _, pos)
| Self::Switch(_, _, pos)
| Self::While(_, _, pos)
- | Self::Loop(_, pos)
+ | Self::Do(_, _, _, pos)
| Self::For(_, _, pos)
| Self::Return((_, pos), _, _)
| Self::Let(_, _, _, pos)
@@ -717,7 +717,6 @@ impl Stmt {
Self::If(_, _, _)
| Self::Switch(_, _, _)
| Self::While(_, _, _)
- | Self::Loop(_, _)
| Self::For(_, _, _)
| Self::Block(_, _)
| Self::TryCatch(_, _, _) => true,
@@ -729,6 +728,7 @@ impl Stmt {
| Self::Const(_, _, _, _)
| Self::Assignment(_, _)
| Self::Expr(_)
+ | Self::Do(_, _, _, _)
| Self::Continue(_)
| Self::Break(_)
| Self::Return(_, _, _) => false,
@@ -737,7 +737,7 @@ impl Stmt {
Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))]
- Self::Share(_) => false,
+ Self::Share(_) => unreachable!(),
}
}
/// Is this statement _pure_?
@@ -755,8 +755,9 @@ impl Stmt {
&& x.0.values().all(Stmt::is_pure)
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
}
- Self::While(condition, block, _) => condition.is_pure() && block.is_pure(),
- Self::Loop(block, _) => block.is_pure(),
+ Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
+ condition.is_pure() && block.is_pure()
+ }
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),
diff --git a/src/engine.rs b/src/engine.rs
index d15ff852..de065023 100644
--- a/src/engine.rs
+++ b/src/engine.rs
@@ -1028,7 +1028,7 @@ impl Engine {
})?;
}
- Ok(Default::default())
+ Ok((Dynamic::UNIT, true))
}
// xxx[rhs]
_ => {
@@ -1193,7 +1193,7 @@ impl Engine {
|err| match *err {
// If there is no setter, no need to feed it back because the property is read-only
EvalAltResult::ErrorDotExpr(_, _) => {
- Ok(Default::default())
+ Ok((Dynamic::UNIT, false))
}
_ => Err(err.fill_position(*x_pos)),
},
@@ -1902,7 +1902,7 @@ impl Engine {
let result = match stmt {
// No-op
- Stmt::Noop(_) => Ok(Default::default()),
+ Stmt::Noop(_) => Ok(Dynamic::UNIT),
// Expression as statement
Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level),
@@ -1935,7 +1935,7 @@ impl Engine {
} else {
*lhs_ptr.as_mut() = rhs_val;
}
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
// Op-assignment - in order of precedence:
ScopeEntryType::Normal => {
@@ -2003,7 +2003,7 @@ impl Engine {
}
}
}
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
}
}
@@ -2045,7 +2045,7 @@ impl Engine {
self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val,
)?;
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
// dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))]
@@ -2053,7 +2053,7 @@ impl Engine {
self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val,
)?;
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
// Non-lvalue expression (should be caught during parsing)
_ => unreachable!(),
@@ -2077,7 +2077,7 @@ impl Engine {
} else if let Some(stmt) = else_block {
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
} else {
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
})
}
@@ -2115,28 +2115,40 @@ impl Engine {
Ok(_) => (),
Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (),
- EvalAltResult::LoopBreak(true, _) => return Ok(Default::default()),
+ EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
_ => return Err(err),
},
}
}
- Ok(false) => return Ok(Default::default()),
+ Ok(false) => return Ok(Dynamic::UNIT),
Err(err) => {
return Err(self.make_type_mismatch_err::(err, expr.position()))
}
}
},
- // Loop statement
- Stmt::Loop(block, _) => loop {
- match self.eval_stmt(scope, mods, state, lib, this_ptr, block, level) {
+ // Do loop
+ Stmt::Do(body, expr, is_while, _) => loop {
+ match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
Ok(_) => (),
Err(err) => match *err {
- EvalAltResult::LoopBreak(false, _) => (),
- EvalAltResult::LoopBreak(true, _) => return Ok(Default::default()),
+ EvalAltResult::LoopBreak(false, _) => continue,
+ EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
_ => return Err(err),
},
}
+
+ match self
+ .eval_expr(scope, mods, state, lib, this_ptr, expr, level)?
+ .as_bool()
+ {
+ Ok(true) if !*is_while => return Ok(Dynamic::UNIT),
+ Ok(false) if *is_while => return Ok(Dynamic::UNIT),
+ Ok(_) => (),
+ Err(err) => {
+ return Err(self.make_type_mismatch_err::(err, expr.position()))
+ }
+ }
},
// For loop
@@ -2187,7 +2199,7 @@ impl Engine {
state.scope_level -= 1;
scope.rewind(scope.len() - 1);
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
} else {
EvalAltResult::ErrorFor(expr.position()).into()
}
@@ -2312,7 +2324,7 @@ impl Engine {
if let Some(alias) = _alias {
scope.add_entry_alias(scope.len() - 1, alias);
}
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
// Import statement
@@ -2344,7 +2356,7 @@ impl Engine {
state.modules += 1;
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
} else {
Err(
EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position())
@@ -2369,7 +2381,7 @@ impl Engine {
.into();
}
}
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
// Share statement
@@ -2386,7 +2398,7 @@ impl Engine {
}
_ => (),
}
- Ok(Default::default())
+ Ok(Dynamic::UNIT)
}
};
diff --git a/src/optimize.rs b/src/optimize.rs
index 75c2f5e2..51fe2d2a 100644
--- a/src/optimize.rs
+++ b/src/optimize.rs
@@ -271,7 +271,7 @@ fn optimize_stmt_block(
fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
match stmt {
// expr op= expr
- Stmt::Assignment(ref mut x, _) => match x.0 {
+ Stmt::Assignment(x, _) => match x.0 {
Expr::Variable(_) => optimize_expr(&mut x.2, state),
_ => {
optimize_expr(&mut x.0, state);
@@ -290,7 +290,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
optimize_stmt(stmt, state, true);
}
// if expr { Noop }
- Stmt::If(ref mut condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
+ Stmt::If(condition, x, _) if x.1.is_none() && matches!(x.0, Stmt::Noop(_)) => {
state.set_dirty();
let pos = condition.position();
@@ -309,7 +309,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
};
}
// if expr { if_block }
- Stmt::If(ref mut condition, ref mut x, _) if x.1.is_none() => {
+ Stmt::If(condition, x, _) if x.1.is_none() => {
optimize_expr(condition, state);
optimize_stmt(&mut x.0, state, true);
}
@@ -324,7 +324,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
optimize_stmt(stmt, state, true);
}
// if expr { if_block } else { else_block }
- Stmt::If(ref mut condition, ref mut x, _) => {
+ Stmt::If(condition, x, _) => {
optimize_expr(condition, state);
optimize_stmt(&mut x.0, state, true);
if let Some(else_block) = x.1.as_mut() {
@@ -377,11 +377,6 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
state.set_dirty();
*stmt = Stmt::Noop(*pos)
}
- // while true { block } -> loop { block }
- Stmt::While(Expr::BoolConstant(true, _), block, pos) => {
- optimize_stmt(block, state, false);
- *stmt = Stmt::Loop(Box::new(mem::take(block)), *pos)
- }
// while expr { block }
Stmt::While(condition, block, _) => {
optimize_stmt(block, state, false);
@@ -402,32 +397,30 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
_ => (),
}
}
- // loop { block }
- Stmt::Loop(block, _) => {
- optimize_stmt(block, state, false);
-
- match **block {
- // loop { break; } -> Noop
- Stmt::Break(pos) => {
- // Only a single break statement
- state.set_dirty();
- *stmt = Stmt::Noop(pos)
- }
- _ => (),
- }
+ // do { block } while false | do { block } until true -> { block }
+ Stmt::Do(block, Expr::BoolConstant(true, _), false, _)
+ | Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => {
+ state.set_dirty();
+ optimize_stmt(block.as_mut(), state, false);
+ *stmt = mem::take(block.as_mut());
+ }
+ // do { block } while|until expr
+ Stmt::Do(block, condition, _, _) => {
+ optimize_stmt(block.as_mut(), state, false);
+ optimize_expr(condition, state);
}
// for id in expr { block }
- Stmt::For(ref mut iterable, ref mut x, _) => {
+ Stmt::For(iterable, x, _) => {
optimize_expr(iterable, state);
optimize_stmt(&mut x.1, state, false);
}
// let id = expr;
- Stmt::Let(_, Some(ref mut expr), _, _) => optimize_expr(expr, state),
+ Stmt::Let(_, Some(expr), _, _) => optimize_expr(expr, state),
// let id;
Stmt::Let(_, None, _, _) => (),
// import expr as var;
#[cfg(not(feature = "no_module"))]
- Stmt::Import(ref mut expr, _, _) => optimize_expr(expr, state),
+ Stmt::Import(expr, _, _) => optimize_expr(expr, state),
// { block }
Stmt::Block(statements, pos) => {
*stmt = optimize_stmt_block(mem::take(statements), *pos, state, preserve_result, true);
@@ -446,7 +439,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
*stmt = Stmt::Block(statements, pos);
}
// try { block } catch ( var ) { block }
- Stmt::TryCatch(ref mut x, _, _) => {
+ Stmt::TryCatch(x, _, _) => {
optimize_stmt(&mut x.0, state, false);
optimize_stmt(&mut x.2, state, false);
}
@@ -461,7 +454,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
*stmt = Stmt::Block(mem::take(x).into_vec(), *pos);
}
// expr;
- Stmt::Expr(ref mut expr) => optimize_expr(expr, state),
+ Stmt::Expr(expr) => optimize_expr(expr, state),
// return expr;
Stmt::Return(_, Some(ref mut expr), _) => optimize_expr(expr, state),
diff --git a/src/parser.rs b/src/parser.rs
index 9e59f418..aea543c8 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -1970,49 +1970,68 @@ fn parse_if(
}
/// Parse a while loop.
-fn parse_while(
+fn parse_while_loop(
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FunctionsLib,
mut settings: ParseSettings,
) -> Result {
- // 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)?;
- // while guard { body }
- ensure_not_statement_expr(input, "a boolean")?;
- let guard = parse_expr(input, state, lib, settings.level_up())?;
- ensure_not_assignment(input)?;
+ // while|loops ...
+ let (guard, token_pos) = match input.next().unwrap() {
+ (Token::While, pos) => {
+ ensure_not_statement_expr(input, "a boolean")?;
+ (parse_expr(input, state, lib, settings.level_up())?, pos)
+ }
+ (Token::Loop, pos) => (Expr::BoolConstant(true, pos), pos),
+ _ => unreachable!(),
+ };
+ settings.pos = token_pos;
+ ensure_not_assignment(input)?;
settings.is_breakable = true;
let body = Box::new(parse_block(input, state, lib, settings.level_up())?);
Ok(Stmt::While(guard, body, token_pos))
}
-/// Parse a loop statement.
-fn parse_loop(
+/// Parse a do loop.
+fn parse_do(
input: &mut TokenStream,
state: &mut ParseState,
lib: &mut FunctionsLib,
mut settings: ParseSettings,
) -> Result {
- // loop ...
- let token_pos = eat_token(input, Token::Loop);
+ // do ...
+ let token_pos = eat_token(input, Token::Do);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?;
- // loop { body }
+ // do { body } [while|until] guard
settings.is_breakable = true;
let body = Box::new(parse_block(input, state, lib, settings.level_up())?);
- Ok(Stmt::Loop(body, token_pos))
+ let is_while = match input.next().unwrap() {
+ (Token::While, _) => true,
+ (Token::Until, _) => false,
+ (_, pos) => {
+ return Err(
+ PERR::MissingToken(Token::While.into(), "for the do statement".into())
+ .into_err(pos),
+ )
+ }
+ };
+
+ ensure_not_statement_expr(input, "a boolean")?;
+ settings.is_breakable = false;
+ let guard = parse_expr(input, state, lib, settings.level_up())?;
+ ensure_not_assignment(input)?;
+
+ Ok(Stmt::Do(body, guard, is_while, token_pos))
}
/// Parse a for loop.
@@ -2413,8 +2432,10 @@ fn parse_stmt(
}
Token::If => parse_if(input, state, lib, settings.level_up()).map(Some),
- Token::While => parse_while(input, state, lib, settings.level_up()).map(Some),
- Token::Loop => parse_loop(input, state, lib, settings.level_up()).map(Some),
+ Token::While | Token::Loop => {
+ parse_while_loop(input, state, lib, settings.level_up()).map(Some)
+ }
+ Token::Do => parse_do(input, state, lib, settings.level_up()).map(Some),
Token::For => parse_for(input, state, lib, settings.level_up()).map(Some),
Token::Continue if settings.is_breakable => {
diff --git a/src/token.rs b/src/token.rs
index 878025b8..c36a5651 100644
--- a/src/token.rs
+++ b/src/token.rs
@@ -232,8 +232,12 @@ pub enum Token {
Else,
/// `switch`
Switch,
+ /// `do`
+ Do,
/// `while`
While,
+ /// `until`
+ Until,
/// `loop`
Loop,
/// `for`
@@ -380,7 +384,9 @@ impl Token {
If => "if",
Else => "else",
Switch => "switch",
+ Do => "do",
While => "while",
+ Until => "until",
Loop => "loop",
For => "for",
In => "in",
@@ -467,7 +473,9 @@ impl Token {
"if" => If,
"else" => Else,
"switch" => Switch,
+ "do" => Do,
"while" => While,
+ "until" => Until,
"loop" => Loop,
"for" => For,
"in" => In,
@@ -524,8 +532,8 @@ impl Token {
"import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new"
- | "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "do"
- | "each" | "then" | "goto" | "exit" | "match" | "case" | "default" | "void"
+ | "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "each"
+ | "then" | "goto" | "unless" | "exit" | "match" | "case" | "default" | "void"
| "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await" | "yield" => {
Reserved(syntax.into())
}
@@ -586,7 +594,9 @@ impl Token {
Ampersand |
And |
If |
+ Do |
While |
+ Until |
PlusAssign |
MinusAssign |
MultiplyAssign |
@@ -690,8 +700,8 @@ impl Token {
#[cfg(not(feature = "no_module"))]
Import | Export | As => true,
- True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break
- | Return | Throw | Try | Catch => true,
+ True | False | Let | Const | If | Else | Do | While | Until | Loop | For | In
+ | Continue | Break | Return | Throw | Try | Catch => true,
_ => false,
}
diff --git a/tests/while_loop.rs b/tests/while_loop.rs
index bbcd091b..f9b74846 100644
--- a/tests/while_loop.rs
+++ b/tests/while_loop.rs
@@ -24,3 +24,28 @@ fn test_while() -> Result<(), Box> {
Ok(())
}
+
+#[test]
+fn test_do() -> Result<(), Box> {
+ let engine = Engine::new();
+
+ assert_eq!(
+ engine.eval::(
+ r"
+ let x = 0;
+
+ do {
+ x += 1;
+ if x > 5 { break; }
+ if x > 3 { continue; }
+ x += 3;
+ } while x < 10;
+
+ x
+ ",
+ )?,
+ 6
+ );
+
+ Ok(())
+}