This commit is contained in:
Stephen Chung 2020-11-20 22:23:37 +08:00
parent b34e7840b0
commit 6069a4cf55
10 changed files with 173 additions and 78 deletions

View File

@ -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`. * `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 New features
------------ ------------
* `switch` statement. * `switch` statement.
* `do ... while` and `do ... until` statement.
* `Engine::register_module` to register a module as a sub-module in the global namespace. * `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. * `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. * `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.

View File

@ -79,6 +79,7 @@ The Rhai Scripting Language
9. [If Statement](language/if.md) 9. [If Statement](language/if.md)
10. [Switch Expression](language/switch.md) 10. [Switch Expression](language/switch.md)
11. [While Loop](language/while.md) 11. [While Loop](language/while.md)
11. [Do Loop](language/do.md)
12. [Loop Statement](language/loop.md) 12. [Loop Statement](language/loop.md)
13. [For Loop](language/for.md) 13. [For Loop](language/for.md)
14. [Return Values](language/return.md) 14. [Return Values](language/return.md)

View File

@ -14,7 +14,9 @@ Keywords List
| `if` | if statement | | no | | | `if` | if statement | | no | |
| `else` | else block of if statement | | no | | | `else` | else block of if statement | | no | |
| `switch` | matching | | no | | | `switch` | matching | | no | |
| `while` | while loop | | no | | | `do` | looping | | no | |
| `while` | 1) while loop<br/>2) condition for do loop | | no | |
| `until` | do loop | | no | |
| `loop` | infinite loop | | no | | | `loop` | infinite loop | | no | |
| `for` | for loop | | no | | | `for` | for loop | | no | |
| `in` | 1) containment test<br/>2) part of for loop | | no | | | `in` | 1) containment test<br/>2) part of for loop | | no | |
@ -48,11 +50,11 @@ Reserved Keywords
| `var` | variable declaration | | `var` | variable declaration |
| `static` | variable declaration | | `static` | variable declaration |
| `shared` | share value | | `shared` | share value |
| `do` | looping |
| `each` | looping | | `each` | looping |
| `then` | control flow | | `then` | control flow |
| `goto` | control flow | | `goto` | control flow |
| `exit` | control flow | | `exit` | control flow |
| `unless` | control flow |
| `match` | matching | | `match` | matching |
| `case` | matching | | `case` | matching |
| `public` | function/field access | | `public` | function/field access |

28
doc/src/language/do.md Normal file
View File

@ -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;
```

View File

@ -601,8 +601,8 @@ pub enum Stmt {
), ),
/// `while` expr `{` stmt `}` /// `while` expr `{` stmt `}`
While(Expr, Box<Stmt>, Position), While(Expr, Box<Stmt>, Position),
/// `loop` `{` stmt `}` /// `do` `{` stmt `}` `while`|`until` expr
Loop(Box<Stmt>, Position), Do(Box<Stmt>, Expr, bool, Position),
/// `for` id `in` expr `{` stmt `}` /// `for` id `in` expr `{` stmt `}`
For(Expr, Box<(String, Stmt)>, Position), For(Expr, Box<(String, Stmt)>, Position),
/// \[`export`\] `let` id `=` expr /// \[`export`\] `let` id `=` expr
@ -660,7 +660,7 @@ impl Stmt {
| Self::If(_, _, pos) | Self::If(_, _, pos)
| Self::Switch(_, _, pos) | Self::Switch(_, _, pos)
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Loop(_, pos) | Self::Do(_, _, _, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::Return((_, pos), _, _) | Self::Return((_, pos), _, _)
| Self::Let(_, _, _, pos) | Self::Let(_, _, _, pos)
@ -689,7 +689,7 @@ impl Stmt {
| Self::If(_, _, pos) | Self::If(_, _, pos)
| Self::Switch(_, _, pos) | Self::Switch(_, _, pos)
| Self::While(_, _, pos) | Self::While(_, _, pos)
| Self::Loop(_, pos) | Self::Do(_, _, _, pos)
| Self::For(_, _, pos) | Self::For(_, _, pos)
| Self::Return((_, pos), _, _) | Self::Return((_, pos), _, _)
| Self::Let(_, _, _, pos) | Self::Let(_, _, _, pos)
@ -717,7 +717,6 @@ impl Stmt {
Self::If(_, _, _) Self::If(_, _, _)
| Self::Switch(_, _, _) | Self::Switch(_, _, _)
| Self::While(_, _, _) | Self::While(_, _, _)
| Self::Loop(_, _)
| Self::For(_, _, _) | Self::For(_, _, _)
| Self::Block(_, _) | Self::Block(_, _)
| Self::TryCatch(_, _, _) => true, | Self::TryCatch(_, _, _) => true,
@ -729,6 +728,7 @@ impl Stmt {
| Self::Const(_, _, _, _) | Self::Const(_, _, _, _)
| Self::Assignment(_, _) | Self::Assignment(_, _)
| Self::Expr(_) | Self::Expr(_)
| Self::Do(_, _, _, _)
| Self::Continue(_) | Self::Continue(_)
| Self::Break(_) | Self::Break(_)
| Self::Return(_, _, _) => false, | Self::Return(_, _, _) => false,
@ -737,7 +737,7 @@ impl Stmt {
Self::Import(_, _, _) | Self::Export(_, _) => false, Self::Import(_, _, _) | Self::Export(_, _) => false,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
Self::Share(_) => false, Self::Share(_) => unreachable!(),
} }
} }
/// Is this statement _pure_? /// Is this statement _pure_?
@ -755,8 +755,9 @@ impl Stmt {
&& x.0.values().all(Stmt::is_pure) && x.0.values().all(Stmt::is_pure)
&& x.1.as_ref().map(Stmt::is_pure).unwrap_or(true) && x.1.as_ref().map(Stmt::is_pure).unwrap_or(true)
} }
Self::While(condition, block, _) => condition.is_pure() && block.is_pure(), Self::While(condition, block, _) | Self::Do(block, condition, _, _) => {
Self::Loop(block, _) => block.is_pure(), condition.is_pure() && block.is_pure()
}
Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(), Self::For(iterable, x, _) => iterable.is_pure() && x.1.is_pure(),
Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false, Self::Let(_, _, _, _) | Self::Const(_, _, _, _) | Self::Assignment(_, _) => false,
Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()),

View File

@ -1028,7 +1028,7 @@ impl Engine {
})?; })?;
} }
Ok(Default::default()) Ok((Dynamic::UNIT, true))
} }
// xxx[rhs] // xxx[rhs]
_ => { _ => {
@ -1193,7 +1193,7 @@ impl Engine {
|err| match *err { |err| match *err {
// If there is no setter, no need to feed it back because the property is read-only // If there is no setter, no need to feed it back because the property is read-only
EvalAltResult::ErrorDotExpr(_, _) => { EvalAltResult::ErrorDotExpr(_, _) => {
Ok(Default::default()) Ok((Dynamic::UNIT, false))
} }
_ => Err(err.fill_position(*x_pos)), _ => Err(err.fill_position(*x_pos)),
}, },
@ -1902,7 +1902,7 @@ impl Engine {
let result = match stmt { let result = match stmt {
// No-op // No-op
Stmt::Noop(_) => Ok(Default::default()), Stmt::Noop(_) => Ok(Dynamic::UNIT),
// Expression as statement // Expression as statement
Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level), Stmt::Expr(expr) => self.eval_expr(scope, mods, state, lib, this_ptr, expr, level),
@ -1935,7 +1935,7 @@ impl Engine {
} else { } else {
*lhs_ptr.as_mut() = rhs_val; *lhs_ptr.as_mut() = rhs_val;
} }
Ok(Default::default()) Ok(Dynamic::UNIT)
} }
// Op-assignment - in order of precedence: // Op-assignment - in order of precedence:
ScopeEntryType::Normal => { 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( self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val,
)?; )?;
Ok(Default::default()) Ok(Dynamic::UNIT)
} }
// dot_lhs.dot_rhs op= rhs // dot_lhs.dot_rhs op= rhs
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
@ -2053,7 +2053,7 @@ impl Engine {
self.eval_dot_index_chain( self.eval_dot_index_chain(
scope, mods, state, lib, this_ptr, lhs_expr, level, _new_val, 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) // Non-lvalue expression (should be caught during parsing)
_ => unreachable!(), _ => unreachable!(),
@ -2077,7 +2077,7 @@ impl Engine {
} else if let Some(stmt) = else_block { } else if let Some(stmt) = else_block {
self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level) self.eval_stmt(scope, mods, state, lib, this_ptr, stmt, level)
} else { } else {
Ok(Default::default()) Ok(Dynamic::UNIT)
} }
}) })
} }
@ -2115,28 +2115,40 @@ impl Engine {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (), EvalAltResult::LoopBreak(false, _) => (),
EvalAltResult::LoopBreak(true, _) => return Ok(Default::default()), EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
_ => return Err(err), _ => return Err(err),
}, },
} }
} }
Ok(false) => return Ok(Default::default()), Ok(false) => return Ok(Dynamic::UNIT),
Err(err) => { Err(err) => {
return Err(self.make_type_mismatch_err::<bool>(err, expr.position())) return Err(self.make_type_mismatch_err::<bool>(err, expr.position()))
} }
} }
}, },
// Loop statement // Do loop
Stmt::Loop(block, _) => loop { Stmt::Do(body, expr, is_while, _) => loop {
match self.eval_stmt(scope, mods, state, lib, this_ptr, block, level) { match self.eval_stmt(scope, mods, state, lib, this_ptr, body, level) {
Ok(_) => (), Ok(_) => (),
Err(err) => match *err { Err(err) => match *err {
EvalAltResult::LoopBreak(false, _) => (), EvalAltResult::LoopBreak(false, _) => continue,
EvalAltResult::LoopBreak(true, _) => return Ok(Default::default()), EvalAltResult::LoopBreak(true, _) => return Ok(Dynamic::UNIT),
_ => return Err(err), _ => 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::<bool>(err, expr.position()))
}
}
}, },
// For loop // For loop
@ -2187,7 +2199,7 @@ impl Engine {
state.scope_level -= 1; state.scope_level -= 1;
scope.rewind(scope.len() - 1); scope.rewind(scope.len() - 1);
Ok(Default::default()) Ok(Dynamic::UNIT)
} else { } else {
EvalAltResult::ErrorFor(expr.position()).into() EvalAltResult::ErrorFor(expr.position()).into()
} }
@ -2312,7 +2324,7 @@ impl Engine {
if let Some(alias) = _alias { if let Some(alias) = _alias {
scope.add_entry_alias(scope.len() - 1, alias); scope.add_entry_alias(scope.len() - 1, alias);
} }
Ok(Default::default()) Ok(Dynamic::UNIT)
} }
// Import statement // Import statement
@ -2344,7 +2356,7 @@ impl Engine {
state.modules += 1; state.modules += 1;
Ok(Default::default()) Ok(Dynamic::UNIT)
} else { } else {
Err( Err(
EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position()) EvalAltResult::ErrorModuleNotFound(path.to_string(), expr.position())
@ -2369,7 +2381,7 @@ impl Engine {
.into(); .into();
} }
} }
Ok(Default::default()) Ok(Dynamic::UNIT)
} }
// Share statement // Share statement
@ -2386,7 +2398,7 @@ impl Engine {
} }
_ => (), _ => (),
} }
Ok(Default::default()) Ok(Dynamic::UNIT)
} }
}; };

View File

@ -271,7 +271,7 @@ fn optimize_stmt_block(
fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
match stmt { match stmt {
// expr op= expr // 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), Expr::Variable(_) => optimize_expr(&mut x.2, state),
_ => { _ => {
optimize_expr(&mut x.0, 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); optimize_stmt(stmt, state, true);
} }
// if expr { Noop } // 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(); state.set_dirty();
let pos = condition.position(); let pos = condition.position();
@ -309,7 +309,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
}; };
} }
// if expr { if_block } // 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_expr(condition, state);
optimize_stmt(&mut x.0, state, true); 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); optimize_stmt(stmt, state, true);
} }
// if expr { if_block } else { else_block } // if expr { if_block } else { else_block }
Stmt::If(ref mut condition, ref mut x, _) => { Stmt::If(condition, x, _) => {
optimize_expr(condition, state); optimize_expr(condition, state);
optimize_stmt(&mut x.0, state, true); optimize_stmt(&mut x.0, state, true);
if let Some(else_block) = x.1.as_mut() { 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(); state.set_dirty();
*stmt = Stmt::Noop(*pos) *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 } // while expr { block }
Stmt::While(condition, block, _) => { Stmt::While(condition, block, _) => {
optimize_stmt(block, state, false); optimize_stmt(block, state, false);
@ -402,32 +397,30 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) {
_ => (), _ => (),
} }
} }
// loop { block } // do { block } while false | do { block } until true -> { block }
Stmt::Loop(block, _) => { Stmt::Do(block, Expr::BoolConstant(true, _), false, _)
optimize_stmt(block, state, false); | Stmt::Do(block, Expr::BoolConstant(false, _), true, _) => {
match **block {
// loop { break; } -> Noop
Stmt::Break(pos) => {
// Only a single break statement
state.set_dirty(); state.set_dirty();
*stmt = Stmt::Noop(pos) 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 } // for id in expr { block }
Stmt::For(ref mut iterable, ref mut x, _) => { Stmt::For(iterable, x, _) => {
optimize_expr(iterable, state); optimize_expr(iterable, state);
optimize_stmt(&mut x.1, state, false); optimize_stmt(&mut x.1, state, false);
} }
// let id = expr; // let id = expr;
Stmt::Let(_, Some(ref mut expr), _, _) => optimize_expr(expr, state), Stmt::Let(_, Some(expr), _, _) => optimize_expr(expr, state),
// let id; // let id;
Stmt::Let(_, None, _, _) => (), Stmt::Let(_, None, _, _) => (),
// import expr as var; // import expr as var;
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Stmt::Import(ref mut expr, _, _) => optimize_expr(expr, state), Stmt::Import(expr, _, _) => optimize_expr(expr, state),
// { block } // { block }
Stmt::Block(statements, pos) => { Stmt::Block(statements, pos) => {
*stmt = optimize_stmt_block(mem::take(statements), *pos, state, preserve_result, true); *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); *stmt = Stmt::Block(statements, pos);
} }
// try { block } catch ( var ) { block } // try { block } catch ( var ) { block }
Stmt::TryCatch(ref mut x, _, _) => { Stmt::TryCatch(x, _, _) => {
optimize_stmt(&mut x.0, state, false); optimize_stmt(&mut x.0, state, false);
optimize_stmt(&mut x.2, 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); *stmt = Stmt::Block(mem::take(x).into_vec(), *pos);
} }
// expr; // expr;
Stmt::Expr(ref mut expr) => optimize_expr(expr, state), Stmt::Expr(expr) => optimize_expr(expr, state),
// return expr; // return expr;
Stmt::Return(_, Some(ref mut expr), _) => optimize_expr(expr, state), Stmt::Return(_, Some(ref mut expr), _) => optimize_expr(expr, state),

View File

@ -1970,49 +1970,68 @@ fn parse_if(
} }
/// Parse a while loop. /// Parse a while loop.
fn parse_while( fn parse_while_loop(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// while ...
let token_pos = eat_token(input, Token::While);
settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// while guard { body } // while|loops ...
let (guard, token_pos) = match input.next().unwrap() {
(Token::While, pos) => {
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let guard = parse_expr(input, state, lib, settings.level_up())?; (parse_expr(input, state, lib, settings.level_up())?, pos)
ensure_not_assignment(input)?; }
(Token::Loop, pos) => (Expr::BoolConstant(true, pos), pos),
_ => unreachable!(),
};
settings.pos = token_pos;
ensure_not_assignment(input)?;
settings.is_breakable = true; settings.is_breakable = true;
let body = Box::new(parse_block(input, state, lib, settings.level_up())?); let body = Box::new(parse_block(input, state, lib, settings.level_up())?);
Ok(Stmt::While(guard, body, token_pos)) Ok(Stmt::While(guard, body, token_pos))
} }
/// Parse a loop statement. /// Parse a do loop.
fn parse_loop( fn parse_do(
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
lib: &mut FunctionsLib, lib: &mut FunctionsLib,
mut settings: ParseSettings, mut settings: ParseSettings,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// loop ... // do ...
let token_pos = eat_token(input, Token::Loop); let token_pos = eat_token(input, Token::Do);
settings.pos = token_pos; settings.pos = token_pos;
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
// loop { body } // do { body } [while|until] guard
settings.is_breakable = true; settings.is_breakable = true;
let body = Box::new(parse_block(input, state, lib, settings.level_up())?); 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. /// Parse a for loop.
@ -2413,8 +2432,10 @@ fn parse_stmt(
} }
Token::If => parse_if(input, state, lib, settings.level_up()).map(Some), 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::While | Token::Loop => {
Token::Loop => parse_loop(input, state, lib, settings.level_up()).map(Some), 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::For => parse_for(input, state, lib, settings.level_up()).map(Some),
Token::Continue if settings.is_breakable => { Token::Continue if settings.is_breakable => {

View File

@ -232,8 +232,12 @@ pub enum Token {
Else, Else,
/// `switch` /// `switch`
Switch, Switch,
/// `do`
Do,
/// `while` /// `while`
While, While,
/// `until`
Until,
/// `loop` /// `loop`
Loop, Loop,
/// `for` /// `for`
@ -380,7 +384,9 @@ impl Token {
If => "if", If => "if",
Else => "else", Else => "else",
Switch => "switch", Switch => "switch",
Do => "do",
While => "while", While => "while",
Until => "until",
Loop => "loop", Loop => "loop",
For => "for", For => "for",
In => "in", In => "in",
@ -467,7 +473,9 @@ impl Token {
"if" => If, "if" => If,
"else" => Else, "else" => Else,
"switch" => Switch, "switch" => Switch,
"do" => Do,
"while" => While, "while" => While,
"until" => Until,
"loop" => Loop, "loop" => Loop,
"for" => For, "for" => For,
"in" => In, "in" => In,
@ -524,8 +532,8 @@ impl Token {
"import" | "export" | "as" => Reserved(syntax.into()), "import" | "export" | "as" => Reserved(syntax.into()),
"===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new" "===" | "!==" | "->" | "<-" | ":=" | "::<" | "(*" | "*)" | "#" | "public" | "new"
| "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "do" | "use" | "module" | "package" | "var" | "static" | "shared" | "with" | "each"
| "each" | "then" | "goto" | "exit" | "match" | "case" | "default" | "void" | "then" | "goto" | "unless" | "exit" | "match" | "case" | "default" | "void"
| "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await" | "yield" => { | "null" | "nil" | "spawn" | "thread" | "go" | "sync" | "async" | "await" | "yield" => {
Reserved(syntax.into()) Reserved(syntax.into())
} }
@ -586,7 +594,9 @@ impl Token {
Ampersand | Ampersand |
And | And |
If | If |
Do |
While | While |
Until |
PlusAssign | PlusAssign |
MinusAssign | MinusAssign |
MultiplyAssign | MultiplyAssign |
@ -690,8 +700,8 @@ impl Token {
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Import | Export | As => true, Import | Export | As => true,
True | False | Let | Const | If | Else | While | Loop | For | In | Continue | Break True | False | Let | Const | If | Else | Do | While | Until | Loop | For | In
| Return | Throw | Try | Catch => true, | Continue | Break | Return | Throw | Try | Catch => true,
_ => false, _ => false,
} }

View File

@ -24,3 +24,28 @@ fn test_while() -> Result<(), Box<EvalAltResult>> {
Ok(()) Ok(())
} }
#[test]
fn test_do() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new();
assert_eq!(
engine.eval::<INT>(
r"
let x = 0;
do {
x += 1;
if x > 5 { break; }
if x > 3 { continue; }
x += 3;
} while x < 10;
x
",
)?,
6
);
Ok(())
}