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`.
* `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.

View File

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

View File

@ -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<br/>2) condition for do loop | | no | |
| `until` | do loop | | no | |
| `loop` | infinite loop | | no | |
| `for` | for loop | | no | |
| `in` | 1) containment test<br/>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 |

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, Box<Stmt>, Position),
/// `loop` `{` stmt `}`
Loop(Box<Stmt>, Position),
/// `do` `{` stmt `}` `while`|`until` expr
Do(Box<Stmt>, 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()),

View File

@ -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::<bool>(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::<bool>(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)
}
};

View File

@ -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
// 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();
*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 }
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),

View File

@ -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<Stmt, ParseError> {
// 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 }
// while|loops ...
let (guard, token_pos) = match input.next().unwrap() {
(Token::While, pos) => {
ensure_not_statement_expr(input, "a boolean")?;
let guard = parse_expr(input, state, lib, settings.level_up())?;
ensure_not_assignment(input)?;
(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<Stmt, ParseError> {
// 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 => {

View File

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

View File

@ -24,3 +24,28 @@ fn test_while() -> Result<(), Box<EvalAltResult>> {
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(())
}