Merge branch 'master' into plugins

This commit is contained in:
Stephen Chung 2020-06-01 14:14:47 +08:00
commit a5d8ce2e49
6 changed files with 157 additions and 88 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai" name = "rhai"
version = "0.15.0" version = "0.16.0"
edition = "2018" edition = "2018"
authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"]
description = "Embedded scripting for Rust" description = "Embedded scripting for Rust"

View File

@ -37,7 +37,7 @@ Features
to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are
pulled in to provide for functionalities that used to be in `std`. pulled in to provide for functionalities that used to be in `std`.
**Note:** Currently, the version is `0.15.0`, so the language and API's may change before they stabilize. **Note:** Currently, the version is `0.16.0`, so the language and API's may change before they stabilize.
What Rhai doesn't do What Rhai doesn't do
-------------------- --------------------
@ -71,7 +71,7 @@ Install the Rhai crate on [`crates.io`](https::/crates.io/crates/rhai/) by addin
```toml ```toml
[dependencies] [dependencies]
rhai = "0.15.0" rhai = "0.16.0"
``` ```
Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/):
@ -427,7 +427,7 @@ are supported.
| `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `ImmutableString` | | `+`, | `+=` | `INT`, `FLOAT` (if not [`no_float`]), `ImmutableString` |
| `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) | | `-`, `*`, `/`, `%`, `~`, | `-=`, `*=`, `/=`, `%=`, `~=` | `INT`, `FLOAT` (if not [`no_float`]) |
| `<<`, `>>`, `^`, | `<<=`, `>>=`, `^=` | `INT` | | `<<`, `>>`, `^`, | `<<=`, `>>=`, `^=` | `INT` |
| `&`, `\|`, | `&=`, `|=` | `INT`, `bool` | | `&`, `\|`, | `&=`, `\|=` | `INT`, `bool` |
| `&&`, `\|\|` | | `bool` | | `&&`, `\|\|` | | `bool` |
| `==`, `!=` | | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` | | `==`, `!=` | | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` |
| `>`, `>=`, `<`, `<=` | | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` | | `>`, `>=`, `<`, `<=` | | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` |
@ -496,7 +496,7 @@ In these cases, use the `compile_expression` and `eval_expression` methods or th
let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?; let result = engine.eval_expression::<i64>("2 + (10 + 10) * 2")?;
``` ```
When evaluation _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments - When evaluating _expressions_, no full-blown statement (e.g. `if`, `while`, `for`) - not even variable assignments -
is supported and will be considered parse errors when encountered. is supported and will be considered parse errors when encountered.
```rust ```rust

View File

@ -1,7 +1,11 @@
Rhai Release Notes Rhai Release Notes
================== ==================
Version 0.14.2 Version 0.16.0
==============
Version 0.15.0
============== ==============
Regression fix Regression fix

View File

@ -622,28 +622,33 @@ impl Expr {
| Self::Or(_) | Self::Or(_)
| Self::True(_) | Self::True(_)
| Self::False(_) | Self::False(_)
| Self::Unit(_) => false, | Self::Unit(_)
| Self::Assignment(_) => false,
Self::StringConstant(_) Self::StringConstant(_)
| Self::Stmt(_) | Self::Stmt(_)
| Self::FnCall(_) | Self::FnCall(_)
| Self::Assignment(_)
| Self::Dot(_) | Self::Dot(_)
| Self::Index(_) | Self::Index(_)
| Self::Array(_) | Self::Array(_)
| Self::Map(_) => match token { | Self::Map(_) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true, Token::LeftBracket => true,
_ => false, _ => false,
}, },
Self::Variable(_) => match token { Self::Variable(_) => match token {
Token::LeftBracket | Token::LeftParen => true, #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
Token::LeftParen => true,
Token::DoubleColon => true, Token::DoubleColon => true,
_ => false, _ => false,
}, },
Self::Property(_) => match token { Self::Property(_) => match token {
Token::LeftBracket | Token::LeftParen => true, #[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
Token::LeftParen => true,
_ => false, _ => false,
}, },
} }
@ -695,7 +700,8 @@ fn parse_paren_expr<'a>(
state: &mut ParseState, state: &mut ParseState,
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
@ -705,7 +711,7 @@ fn parse_paren_expr<'a>(
return Ok(Expr::Unit(pos)); return Ok(Expr::Unit(pos));
} }
let expr = parse_expr(input, state, level + 1, allow_stmt_expr)?; let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
match input.next().unwrap() { match input.next().unwrap() {
// ( xxx ) // ( xxx )
@ -729,7 +735,8 @@ fn parse_call_expr<'a>(
mut modules: Option<Box<ModuleRef>>, mut modules: Option<Box<ModuleRef>>,
begin: Position, begin: Position,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
@ -783,7 +790,7 @@ fn parse_call_expr<'a>(
} }
loop { loop {
args.push(parse_expr(input, state, level + 1, allow_stmt_expr)?); args.push(parse_expr(input, state, level + 1, if_expr, stmt_expr)?);
match input.peek().unwrap() { match input.peek().unwrap() {
// id(...args) // id(...args)
@ -850,13 +857,14 @@ fn parse_index_chain<'a>(
lhs: Expr, lhs: Expr,
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
} }
let idx_expr = parse_expr(input, state, level + 1, allow_stmt_expr)?; let idx_expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
// Check type of indexing - must be integer or string // Check type of indexing - must be integer or string
match &idx_expr { match &idx_expr {
@ -1003,7 +1011,8 @@ fn parse_index_chain<'a>(
idx_expr, idx_expr,
idx_pos, idx_pos,
level + 1, level + 1,
allow_stmt_expr, if_expr,
stmt_expr,
)?; )?;
// Indexing binds to right // Indexing binds to right
Ok(Expr::Index(Box::new((lhs, idx_expr, pos)))) Ok(Expr::Index(Box::new((lhs, idx_expr, pos))))
@ -1037,7 +1046,8 @@ fn parse_array_literal<'a>(
state: &mut ParseState, state: &mut ParseState,
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
@ -1047,7 +1057,8 @@ fn parse_array_literal<'a>(
if !match_token(input, Token::RightBracket)? { if !match_token(input, Token::RightBracket)? {
while !input.peek().unwrap().0.is_eof() { while !input.peek().unwrap().0.is_eof() {
arr.push(parse_expr(input, state, level + 1, allow_stmt_expr)?); let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
arr.push(expr);
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::Comma, _) => eat_token(input, Token::Comma), (Token::Comma, _) => eat_token(input, Token::Comma),
@ -1085,7 +1096,8 @@ fn parse_map_literal<'a>(
state: &mut ParseState, state: &mut ParseState,
pos: Position, pos: Position,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(pos)); return Err(PERR::ExprTooDeep.into_err(pos));
@ -1135,7 +1147,7 @@ fn parse_map_literal<'a>(
} }
}; };
let expr = parse_expr(input, state, level + 1, allow_stmt_expr)?; let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
map.push(((name, pos), expr)); map.push(((name, pos), expr));
@ -1186,7 +1198,8 @@ fn parse_primary<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
let pos = *pos; let pos = *pos;
@ -1197,8 +1210,8 @@ fn parse_primary<'a>(
let (token, _) = match token { let (token, _) = match token {
// { - block statement as expression // { - block statement as expression
Token::LeftBrace if allow_stmt_expr => { Token::LeftBrace if stmt_expr => {
return parse_block(input, state, false, level + 1, allow_stmt_expr) return parse_block(input, state, false, level + 1, if_expr, stmt_expr)
.map(|block| Expr::Stmt(Box::new((block, pos)))); .map(|block| Expr::Stmt(Box::new((block, pos))));
} }
Token::EOF => return Err(PERR::UnexpectedEOF.into_err(pos)), Token::EOF => return Err(PERR::UnexpectedEOF.into_err(pos)),
@ -1215,11 +1228,13 @@ fn parse_primary<'a>(
let index = state.find(&s); let index = state.find(&s);
Expr::Variable(Box::new(((s, pos), None, 0, index))) Expr::Variable(Box::new(((s, pos), None, 0, index)))
} }
Token::LeftParen => parse_paren_expr(input, state, pos, level + 1, allow_stmt_expr)?, Token::LeftParen => parse_paren_expr(input, state, pos, level + 1, if_expr, stmt_expr)?,
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
Token::LeftBracket => parse_array_literal(input, state, pos, level + 1, allow_stmt_expr)?, Token::LeftBracket => {
parse_array_literal(input, state, pos, level + 1, if_expr, stmt_expr)?
}
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Token::MapStart => parse_map_literal(input, state, pos, level + 1, allow_stmt_expr)?, Token::MapStart => parse_map_literal(input, state, pos, level + 1, if_expr, stmt_expr)?,
Token::True => Expr::True(pos), Token::True => Expr::True(pos),
Token::False => Expr::False(pos), Token::False => Expr::False(pos),
Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), Token::LexError(err) => return Err(PERR::BadInput(err.to_string()).into_err(pos)),
@ -1242,7 +1257,16 @@ fn parse_primary<'a>(
// Function call // Function call
(Expr::Variable(x), Token::LeftParen) => { (Expr::Variable(x), Token::LeftParen) => {
let ((name, pos), modules, _, _) = *x; let ((name, pos), modules, _, _) = *x;
parse_call_expr(input, state, name, modules, pos, level + 1, allow_stmt_expr)? parse_call_expr(
input,
state,
name,
modules,
pos,
level + 1,
if_expr,
stmt_expr,
)?
} }
(Expr::Property(_), _) => unreachable!(), (Expr::Property(_), _) => unreachable!(),
// module access // module access
@ -1264,10 +1288,14 @@ fn parse_primary<'a>(
// Indexing // Indexing
#[cfg(not(feature = "no_index"))] #[cfg(not(feature = "no_index"))]
(expr, Token::LeftBracket) => { (expr, Token::LeftBracket) => {
parse_index_chain(input, state, expr, token_pos, level + 1, allow_stmt_expr)? parse_index_chain(input, state, expr, token_pos, level + 1, if_expr, stmt_expr)?
} }
// Unknown postfix operator // Unknown postfix operator
(expr, token) => panic!("unknown postfix operator {:?} for {:?}", token, expr), (expr, token) => panic!(
"unknown postfix operator '{}' for {:?}",
token.syntax(),
expr
),
} }
} }
@ -1292,7 +1320,8 @@ fn parse_unary<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
let pos = *pos; let pos = *pos;
@ -1303,15 +1332,15 @@ fn parse_unary<'a>(
match token { match token {
// If statement is allowed to act as expressions // If statement is allowed to act as expressions
Token::If => Ok(Expr::Stmt(Box::new(( Token::If if if_expr => Ok(Expr::Stmt(Box::new((
parse_if(input, state, false, level + 1, allow_stmt_expr)?, parse_if(input, state, false, level + 1, if_expr, stmt_expr)?,
pos, pos,
)))), )))),
// -expr // -expr
Token::UnaryMinus => { Token::UnaryMinus => {
let pos = eat_token(input, Token::UnaryMinus); let pos = eat_token(input, Token::UnaryMinus);
match parse_unary(input, state, level + 1, allow_stmt_expr)? { match parse_unary(input, state, level + 1, if_expr, stmt_expr)? {
// Negative integer // Negative integer
Expr::IntegerConstant(x) => { Expr::IntegerConstant(x) => {
let (num, pos) = *x; let (num, pos) = *x;
@ -1360,13 +1389,14 @@ fn parse_unary<'a>(
// +expr // +expr
Token::UnaryPlus => { Token::UnaryPlus => {
eat_token(input, Token::UnaryPlus); eat_token(input, Token::UnaryPlus);
parse_unary(input, state, level + 1, allow_stmt_expr) parse_unary(input, state, level + 1, if_expr, stmt_expr)
} }
// !expr // !expr
Token::Bang => { Token::Bang => {
let pos = eat_token(input, Token::Bang); let pos = eat_token(input, Token::Bang);
let mut args = StaticVec::new(); let mut args = StaticVec::new();
args.push(parse_primary(input, state, level + 1, allow_stmt_expr)?); let expr = parse_primary(input, state, level + 1, if_expr, stmt_expr)?;
args.push(expr);
let op = "!"; let op = "!";
let hash = calc_fn_hash(empty(), op, 2, empty()); let hash = calc_fn_hash(empty(), op, 2, empty());
@ -1382,7 +1412,7 @@ fn parse_unary<'a>(
// <EOF> // <EOF>
Token::EOF => Err(PERR::UnexpectedEOF.into_err(pos)), Token::EOF => Err(PERR::UnexpectedEOF.into_err(pos)),
// All other tokens // All other tokens
_ => parse_primary(input, state, level + 1, allow_stmt_expr), _ => parse_primary(input, state, level + 1, if_expr, stmt_expr),
} }
} }
@ -1442,7 +1472,8 @@ fn parse_op_assignment_stmt<'a>(
state: &mut ParseState, state: &mut ParseState,
lhs: Expr, lhs: Expr,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (token, pos) = input.peek().unwrap(); let (token, pos) = input.peek().unwrap();
let pos = *pos; let pos = *pos;
@ -1470,7 +1501,7 @@ fn parse_op_assignment_stmt<'a>(
}; };
let (_, pos) = input.next().unwrap(); let (_, pos) = input.next().unwrap();
let rhs = parse_expr(input, state, level + 1, allow_stmt_expr)?; let rhs = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
make_assignment_stmt(op, state, lhs, rhs, pos) make_assignment_stmt(op, state, lhs, rhs, pos)
} }
@ -1685,7 +1716,8 @@ fn parse_binary_op<'a>(
parent_precedence: u8, parent_precedence: u8,
lhs: Expr, lhs: Expr,
mut level: usize, mut level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
if level > state.max_expr_depth { if level > state.max_expr_depth {
return Err(PERR::ExprTooDeep.into_err(lhs.position())); return Err(PERR::ExprTooDeep.into_err(lhs.position()));
@ -1706,14 +1738,14 @@ fn parse_binary_op<'a>(
let (op_token, pos) = input.next().unwrap(); let (op_token, pos) = input.next().unwrap();
let rhs = parse_unary(input, state, level, allow_stmt_expr)?; let rhs = parse_unary(input, state, level, if_expr, stmt_expr)?;
let next_precedence = input.peek().unwrap().0.precedence(); let next_precedence = input.peek().unwrap().0.precedence();
// Bind to right if the next operator has higher precedence // Bind to right if the next operator has higher precedence
// If same precedence, then check if the operator binds right // If same precedence, then check if the operator binds right
let rhs = if (precedence == next_precedence && bind_right) || precedence < next_precedence { let rhs = if (precedence == next_precedence && bind_right) || precedence < next_precedence {
parse_binary_op(input, state, precedence, rhs, level, allow_stmt_expr)? parse_binary_op(input, state, precedence, rhs, level, if_expr, stmt_expr)?
} else { } else {
// Otherwise bind to left (even if next operator has the same precedence) // Otherwise bind to left (even if next operator has the same precedence)
rhs rhs
@ -1801,7 +1833,8 @@ fn parse_expr<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Expr, ParseError> { ) -> Result<Expr, ParseError> {
let (_, pos) = input.peek().unwrap(); let (_, pos) = input.peek().unwrap();
@ -1809,8 +1842,8 @@ fn parse_expr<'a>(
return Err(PERR::ExprTooDeep.into_err(*pos)); return Err(PERR::ExprTooDeep.into_err(*pos));
} }
let lhs = parse_unary(input, state, level + 1, allow_stmt_expr)?; let lhs = parse_unary(input, state, level + 1, if_expr, stmt_expr)?;
parse_binary_op(input, state, 1, lhs, level + 1, allow_stmt_expr) parse_binary_op(input, state, 1, lhs, level + 1, if_expr, stmt_expr)
} }
/// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`). /// Make sure that the expression is not a statement expression (i.e. wrapped in `{}`).
@ -1861,7 +1894,8 @@ fn parse_if<'a>(
state: &mut ParseState, state: &mut ParseState,
breakable: bool, breakable: bool,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// if ... // if ...
let pos = eat_token(input, Token::If); let pos = eat_token(input, Token::If);
@ -1872,18 +1906,18 @@ fn parse_if<'a>(
// if guard { if_body } // if guard { if_body }
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let guard = parse_expr(input, state, level + 1, allow_stmt_expr)?; let guard = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
let if_body = parse_block(input, state, breakable, level + 1, allow_stmt_expr)?; let if_body = parse_block(input, state, breakable, level + 1, if_expr, stmt_expr)?;
// if guard { if_body } else ... // if guard { if_body } else ...
let else_body = if match_token(input, Token::Else).unwrap_or(false) { let else_body = if match_token(input, Token::Else).unwrap_or(false) {
Some(if let (Token::If, _) = input.peek().unwrap() { Some(if let (Token::If, _) = input.peek().unwrap() {
// if guard { if_body } else if ... // if guard { if_body } else if ...
parse_if(input, state, breakable, level + 1, allow_stmt_expr)? parse_if(input, state, breakable, level + 1, if_expr, stmt_expr)?
} else { } else {
// if guard { if_body } else { else-body } // if guard { if_body } else { else-body }
parse_block(input, state, breakable, level + 1, allow_stmt_expr)? parse_block(input, state, breakable, level + 1, if_expr, stmt_expr)?
}) })
} else { } else {
None None
@ -1897,7 +1931,8 @@ fn parse_while<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// while ... // while ...
let pos = eat_token(input, Token::While); let pos = eat_token(input, Token::While);
@ -1908,9 +1943,9 @@ fn parse_while<'a>(
// while guard { body } // while guard { body }
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let guard = parse_expr(input, state, level + 1, allow_stmt_expr)?; let guard = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
let body = parse_block(input, state, true, level + 1, allow_stmt_expr)?; let body = parse_block(input, state, true, level + 1, if_expr, stmt_expr)?;
Ok(Stmt::While(Box::new((guard, body)))) Ok(Stmt::While(Box::new((guard, body))))
} }
@ -1920,7 +1955,8 @@ fn parse_loop<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// loop ... // loop ...
let pos = eat_token(input, Token::Loop); let pos = eat_token(input, Token::Loop);
@ -1930,7 +1966,7 @@ fn parse_loop<'a>(
} }
// loop { body } // loop { body }
let body = parse_block(input, state, true, level + 1, allow_stmt_expr)?; let body = parse_block(input, state, true, level + 1, if_expr, stmt_expr)?;
Ok(Stmt::Loop(Box::new(body))) Ok(Stmt::Loop(Box::new(body)))
} }
@ -1940,7 +1976,8 @@ fn parse_for<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// for ... // for ...
let pos = eat_token(input, Token::For); let pos = eat_token(input, Token::For);
@ -1975,12 +2012,12 @@ fn parse_for<'a>(
// for name in expr { body } // for name in expr { body }
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let expr = parse_expr(input, state, level + 1, allow_stmt_expr)?; let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
let prev_len = state.len(); let prev_len = state.len();
state.push((name.clone(), ScopeEntryType::Normal)); state.push((name.clone(), ScopeEntryType::Normal));
let body = parse_block(input, state, true, level + 1, allow_stmt_expr)?; let body = parse_block(input, state, true, level + 1, if_expr, stmt_expr)?;
state.truncate(prev_len); state.truncate(prev_len);
@ -1993,7 +2030,8 @@ fn parse_let<'a>(
state: &mut ParseState, state: &mut ParseState,
var_type: ScopeEntryType, var_type: ScopeEntryType,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// let/const... (specified in `var_type`) // let/const... (specified in `var_type`)
let (_, pos) = input.next().unwrap(); let (_, pos) = input.next().unwrap();
@ -2012,7 +2050,7 @@ fn parse_let<'a>(
// let name = ... // let name = ...
if match_token(input, Token::Equals)? { if match_token(input, Token::Equals)? {
// let name = expr // let name = expr
let init_value = parse_expr(input, state, level + 1, allow_stmt_expr)?; let init_value = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
match var_type { match var_type {
// let name = expr // let name = expr
@ -2054,7 +2092,8 @@ fn parse_import<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// import ... // import ...
let pos = eat_token(input, Token::Import); let pos = eat_token(input, Token::Import);
@ -2064,7 +2103,7 @@ fn parse_import<'a>(
} }
// import expr ... // import expr ...
let expr = parse_expr(input, state, level + 1, allow_stmt_expr)?; let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
// import expr as ... // import expr as ...
match input.next().unwrap() { match input.next().unwrap() {
@ -2160,7 +2199,8 @@ fn parse_block<'a>(
state: &mut ParseState, state: &mut ParseState,
breakable: bool, breakable: bool,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
// Must start with { // Must start with {
let pos = match input.next().unwrap() { let pos = match input.next().unwrap() {
@ -2184,7 +2224,15 @@ fn parse_block<'a>(
while !match_token(input, Token::RightBrace)? { while !match_token(input, Token::RightBrace)? {
// Parse statements inside the block // Parse statements inside the block
let stmt = parse_stmt(input, state, breakable, false, level + 1, allow_stmt_expr)?; let stmt = parse_stmt(
input,
state,
breakable,
false,
level + 1,
if_expr,
stmt_expr,
)?;
// See if it needs a terminating semicolon // See if it needs a terminating semicolon
let need_semicolon = !stmt.is_self_terminated(); let need_semicolon = !stmt.is_self_terminated();
@ -2231,7 +2279,8 @@ fn parse_expr_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>, input: &mut Peekable<TokenIterator<'a>>,
state: &mut ParseState, state: &mut ParseState,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
let (_, pos) = input.peek().unwrap(); let (_, pos) = input.peek().unwrap();
@ -2239,8 +2288,8 @@ fn parse_expr_stmt<'a>(
return Err(PERR::ExprTooDeep.into_err(*pos)); return Err(PERR::ExprTooDeep.into_err(*pos));
} }
let expr = parse_expr(input, state, level + 1, allow_stmt_expr)?; let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
let expr = parse_op_assignment_stmt(input, state, expr, level + 1, allow_stmt_expr)?; let expr = parse_op_assignment_stmt(input, state, expr, level + 1, if_expr, stmt_expr)?;
Ok(Stmt::Expr(Box::new(expr))) Ok(Stmt::Expr(Box::new(expr)))
} }
@ -2251,7 +2300,8 @@ fn parse_stmt<'a>(
breakable: bool, breakable: bool,
is_global: bool, is_global: bool,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<Stmt, ParseError> { ) -> Result<Stmt, ParseError> {
use ScopeEntryType::{Constant, Normal}; use ScopeEntryType::{Constant, Normal};
@ -2268,7 +2318,7 @@ fn parse_stmt<'a>(
// Semicolon - empty statement // Semicolon - empty statement
Token::SemiColon => Ok(Stmt::Noop(*pos)), Token::SemiColon => Ok(Stmt::Noop(*pos)),
Token::LeftBrace => parse_block(input, state, breakable, level + 1, allow_stmt_expr), Token::LeftBrace => parse_block(input, state, breakable, level + 1, if_expr, stmt_expr),
// fn ... // fn ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -2276,10 +2326,10 @@ fn parse_stmt<'a>(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Fn => unreachable!(), Token::Fn => unreachable!(),
Token::If => parse_if(input, state, breakable, level + 1, allow_stmt_expr), Token::If => parse_if(input, state, breakable, level + 1, if_expr, stmt_expr),
Token::While => parse_while(input, state, level + 1, allow_stmt_expr), Token::While => parse_while(input, state, level + 1, if_expr, stmt_expr),
Token::Loop => parse_loop(input, state, level + 1, allow_stmt_expr), Token::Loop => parse_loop(input, state, level + 1, if_expr, stmt_expr),
Token::For => parse_for(input, state, level + 1, allow_stmt_expr), Token::For => parse_for(input, state, level + 1, if_expr, stmt_expr),
Token::Continue if breakable => { Token::Continue if breakable => {
let pos = eat_token(input, Token::Continue); let pos = eat_token(input, Token::Continue);
@ -2309,7 +2359,7 @@ fn parse_stmt<'a>(
} }
// `return` or `throw` with expression // `return` or `throw` with expression
(_, _) => { (_, _) => {
let expr = parse_expr(input, state, level + 1, allow_stmt_expr)?; let expr = parse_expr(input, state, level + 1, if_expr, stmt_expr)?;
let pos = expr.position(); let pos = expr.position();
Ok(Stmt::ReturnWithVal(Box::new(( Ok(Stmt::ReturnWithVal(Box::new((
@ -2320,9 +2370,9 @@ fn parse_stmt<'a>(
} }
} }
Token::Let => parse_let(input, state, Normal, level + 1, allow_stmt_expr), Token::Let => parse_let(input, state, Normal, level + 1, if_expr, stmt_expr),
Token::Const => parse_let(input, state, Constant, level + 1, allow_stmt_expr), Token::Const => parse_let(input, state, Constant, level + 1, if_expr, stmt_expr),
Token::Import => parse_import(input, state, level + 1, allow_stmt_expr), Token::Import => parse_import(input, state, level + 1, if_expr, stmt_expr),
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Token::Export if !is_global => Err(PERR::WrongExport.into_err(*pos)), Token::Export if !is_global => Err(PERR::WrongExport.into_err(*pos)),
@ -2330,7 +2380,7 @@ fn parse_stmt<'a>(
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
Token::Export => parse_export(input, state, level + 1), Token::Export => parse_export(input, state, level + 1),
_ => parse_expr_stmt(input, state, level + 1, allow_stmt_expr), _ => parse_expr_stmt(input, state, level + 1, if_expr, stmt_expr),
} }
} }
@ -2341,7 +2391,8 @@ fn parse_fn<'a>(
state: &mut ParseState, state: &mut ParseState,
access: FnAccess, access: FnAccess,
level: usize, level: usize,
allow_stmt_expr: bool, if_expr: bool,
stmt_expr: bool,
) -> Result<FnDef, ParseError> { ) -> Result<FnDef, ParseError> {
let pos = eat_token(input, Token::Fn); let pos = eat_token(input, Token::Fn);
@ -2412,7 +2463,7 @@ fn parse_fn<'a>(
// Parse function body // Parse function body
let body = match input.peek().unwrap() { let body = match input.peek().unwrap() {
(Token::LeftBrace, _) => parse_block(input, state, false, level + 1, allow_stmt_expr)?, (Token::LeftBrace, _) => parse_block(input, state, false, level + 1, if_expr, stmt_expr)?,
(_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)),
}; };
@ -2435,7 +2486,7 @@ pub fn parse_global_expr<'a>(
max_expr_depth: usize, max_expr_depth: usize,
) -> Result<AST, ParseError> { ) -> Result<AST, ParseError> {
let mut state = ParseState::new(max_expr_depth); let mut state = ParseState::new(max_expr_depth);
let expr = parse_expr(input, &mut state, 0, false)?; let expr = parse_expr(input, &mut state, 0, false, false)?;
match input.peek().unwrap() { match input.peek().unwrap() {
(Token::EOF, _) => (), (Token::EOF, _) => (),
@ -2480,7 +2531,7 @@ fn parse_global_level<'a>(
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
(Token::Fn, _) => { (Token::Fn, _) => {
let mut state = ParseState::new(max_expr_depth.1); let mut state = ParseState::new(max_expr_depth.1);
let func = parse_fn(input, &mut state, access, 0, true)?; let func = parse_fn(input, &mut state, access, 0, true, true)?;
// Qualifiers (none) + function name + number of arguments. // Qualifiers (none) + function name + number of arguments.
let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty()); let hash = calc_fn_hash(empty(), &func.name, func.params.len(), empty());
@ -2499,7 +2550,7 @@ fn parse_global_level<'a>(
} }
} }
// Actual statement // Actual statement
let stmt = parse_stmt(input, &mut state, false, true, 0, true)?; let stmt = parse_stmt(input, &mut state, false, true, 0, true, true)?;
let need_semicolon = !stmt.is_self_terminated(); let need_semicolon = !stmt.is_self_terminated();

View File

@ -12,10 +12,9 @@ fn test_expressions() -> Result<(), Box<EvalAltResult>> {
engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?, engine.eval_expression_with_scope::<INT>(&mut scope, "2 + (x + 10) * 2")?,
42 42
); );
assert_eq!( assert!(engine
engine.eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")?, .eval_expression_with_scope::<INT>(&mut scope, "if x > 0 { 42 } else { 123 }")
42 .is_err());
);
assert!(engine.eval_expression::<()>("40 + 2;").is_err()); assert!(engine.eval_expression::<()>("40 + 2;").is_err());
assert!(engine.eval_expression::<()>("40 + { 2 }").is_err()); assert!(engine.eval_expression::<()>("40 + { 2 }").is_err());

View File

@ -23,9 +23,24 @@ fn test_string() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123"); assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<String>("(42).to_string()")?, "42"); assert_eq!(engine.eval::<String>("to_string(42)")?, "42");
#[cfg(not(feature = "no_index"))]
assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[1]"#)?, 'e');
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<INT>(r#"let y = "hello"; y.len"#)?, 5);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>(r#"let y = "hello"; y.clear(); y.len"#)?,
0
);
assert_eq!(engine.eval::<INT>(r#"let y = "hello"; len(y)"#)?, 5);
#[cfg(not(feature = "no_object"))]
#[cfg(not(feature = "no_index"))]
assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[y.len-1]"#)?, 'o'); assert_eq!(engine.eval::<char>(r#"let y = "hello"; y[y.len-1]"#)?, 'o');
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]