Handle break and return better.

This commit is contained in:
Stephen Chung 2020-03-17 17:33:37 +08:00
parent d2951bfb6b
commit 8efe080412
9 changed files with 242 additions and 142 deletions

View File

@ -36,10 +36,18 @@ fn print_error(input: &str, err: EvalAltResult) {
); );
println!("{}", lines[p.line().unwrap() - 1]); println!("{}", lines[p.line().unwrap() - 1]);
let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
};
println!( println!(
"{}^ {}", "{}^ {}",
padding(" ", p.position().unwrap() - 1), padding(" ", p.position().unwrap() - 1),
err.to_string().replace(&pos_text, "") err_text.replace(&pos_text, "")
); );
} }
} }
@ -86,7 +94,12 @@ fn main() {
.map_err(EvalAltResult::ErrorParsing) .map_err(EvalAltResult::ErrorParsing)
.and_then(|r| { .and_then(|r| {
ast = Some(r); ast = Some(r);
engine.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap()) engine
.consume_ast_with_scope(&mut scope, true, ast.as_ref().unwrap())
.or_else(|err| match err {
EvalAltResult::Return(_, _) => Ok(()),
err => Err(err),
})
}) })
{ {
println!(""); println!("");

View File

@ -31,7 +31,13 @@ fn eprint_error(input: &str, err: EvalAltResult) {
// EOF // EOF
let line = lines.len() - 1; let line = lines.len() - 1;
let pos = lines[line - 1].len(); let pos = lines[line - 1].len();
eprint_line(&lines, line, pos, &err.to_string()); let err_text = match err {
EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => {
format!("Runtime error: {}", err)
}
_ => err.to_string(),
};
eprint_line(&lines, line, pos, &err_text);
} }
p if p.is_none() => { p if p.is_none() => {
// No position // No position

View File

@ -1068,7 +1068,9 @@ impl Engine<'_> {
if *guard_val { if *guard_val {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(_)) => {
return Ok(().into_dynamic())
}
Err(x) => return Err(x), Err(x) => return Err(x),
} }
} else { } else {
@ -1083,7 +1085,7 @@ impl Engine<'_> {
Stmt::Loop(body) => loop { Stmt::Loop(body) => loop {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::LoopBreak) => return Ok(().into_dynamic()), Err(EvalAltResult::ErrorLoopBreak(_)) => return Ok(().into_dynamic()),
Err(x) => return Err(x), Err(x) => return Err(x),
} }
}, },
@ -1102,7 +1104,7 @@ impl Engine<'_> {
match self.eval_stmt(scope, body) { match self.eval_stmt(scope, body) {
Ok(_) => (), Ok(_) => (),
Err(EvalAltResult::LoopBreak) => break, Err(EvalAltResult::ErrorLoopBreak(_)) => break,
Err(x) => return Err(x), Err(x) => return Err(x),
} }
} }
@ -1114,7 +1116,7 @@ impl Engine<'_> {
} }
// Break statement // Break statement
Stmt::Break(_) => Err(EvalAltResult::LoopBreak), Stmt::Break(pos) => Err(EvalAltResult::ErrorLoopBreak(*pos)),
// Empty return // Empty return
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => { Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {

View File

@ -82,12 +82,17 @@ pub enum ParseErrorType {
/// A function definition is missing the parameters list. Wrapped value is the function name. /// A function definition is missing the parameters list. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
FnMissingParams(String), FnMissingParams(String),
/// A function definition is missing the body. Wrapped value is the function name.
#[cfg(not(feature = "no_function"))]
FnMissingBody(String),
/// Assignment to an inappropriate LHS (left-hand-side) expression. /// Assignment to an inappropriate LHS (left-hand-side) expression.
AssignmentToInvalidLHS, AssignmentToInvalidLHS,
/// Assignment to a copy of a value. /// Assignment to a copy of a value.
AssignmentToCopy, AssignmentToCopy,
/// Assignment to an a constant variable. /// Assignment to an a constant variable.
AssignmentToConstant(String), AssignmentToConstant(String),
/// Break statement not inside a loop.
LoopBreak,
} }
/// Error when parsing a script. /// Error when parsing a script.
@ -133,10 +138,13 @@ impl ParseError {
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration",
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration",
#[cfg(not(feature = "no_function"))]
ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function",
ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression", ParseErrorType::AssignmentToInvalidLHS => "Cannot assign to this expression",
ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value", ParseErrorType::AssignmentToCopy => "Cannot assign to this expression because it will only be changing a copy of the value",
ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable." ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant variable.",
ParseErrorType::LoopBreak => "Break statement should only be used inside a loop"
} }
} }
} }
@ -164,6 +172,11 @@ impl fmt::Display for ParseError {
write!(f, "Expecting parameters for function '{}'", s)? write!(f, "Expecting parameters for function '{}'", s)?
} }
#[cfg(not(feature = "no_function"))]
ParseErrorType::FnMissingBody(ref s) => {
write!(f, "Expecting body statement block for function '{}'", s)?
}
ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => { ParseErrorType::MissingRightParen(ref s) | ParseErrorType::MissingRightBrace(ref s) => {
write!(f, "{} for {}", self.desc(), s)? write!(f, "{} for {}", self.desc(), s)?
} }

View File

@ -4,7 +4,7 @@ use crate::any::{Any, Dynamic};
use crate::engine::{ use crate::engine::{
Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT, KEYWORD_TYPE_OF, Engine, FnCallArgs, KEYWORD_DEBUG, KEYWORD_DUMP_AST, KEYWORD_PRINT, KEYWORD_TYPE_OF,
}; };
use crate::parser::{map_dynamic_to_expr, Expr, FnDef, Stmt, AST}; use crate::parser::{map_dynamic_to_expr, Expr, FnDef, ReturnType, Stmt, AST};
use crate::scope::{Scope, ScopeEntry, VariableType}; use crate::scope::{Scope, ScopeEntry, VariableType};
use crate::stdlib::{ use crate::stdlib::{
@ -121,18 +121,40 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
Stmt::Noop(pos) Stmt::Noop(pos)
} }
Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))), Expr::True(_) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
expr => Stmt::While( expr => match optimize_stmt(*stmt, state, false) {
Box::new(optimize_expr(expr, state)), Stmt::Break(pos) => {
Box::new(optimize_stmt(*stmt, state, false)), // Only a single break statement - turn into running the guard expression once
), state.set_dirty();
let mut statements = vec![Stmt::Expr(Box::new(optimize_expr(expr, state)))];
if preserve_result {
statements.push(Stmt::Noop(pos))
}
Stmt::Block(statements, pos)
}
stmt => Stmt::While(Box::new(optimize_expr(expr, state)), Box::new(stmt)),
},
},
Stmt::Loop(stmt) => match optimize_stmt(*stmt, state, false) {
Stmt::Break(pos) => {
// Only a single break statement
state.set_dirty();
Stmt::Noop(pos)
}
stmt => Stmt::Loop(Box::new(stmt)),
}, },
Stmt::Loop(stmt) => Stmt::Loop(Box::new(optimize_stmt(*stmt, state, false))),
Stmt::For(id, expr, stmt) => Stmt::For( Stmt::For(id, expr, stmt) => Stmt::For(
id, id,
Box::new(optimize_expr(*expr, state)), Box::new(optimize_expr(*expr, state)),
Box::new(optimize_stmt(*stmt, state, false)), Box::new(match optimize_stmt(*stmt, state, false) {
Stmt::Break(pos) => {
// Only a single break statement
state.set_dirty();
Stmt::Noop(pos)
}
stmt => stmt,
}),
), ),
Stmt::Let(id, Some(expr), pos) => { Stmt::Let(id, Some(expr), pos) => {
Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos) Stmt::Let(id, Some(Box::new(optimize_expr(*expr, state))), pos)
} }
@ -153,15 +175,12 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
optimize_stmt(stmt, state, preserve_result) // Optimize the statement optimize_stmt(stmt, state, preserve_result) // Optimize the statement
} }
}) })
.enumerate()
.filter(|(i, stmt)| stmt.is_op() || (preserve_result && *i == orig_len - 1)) // Remove no-op's but leave the last one if we need the result
.map(|(_, stmt)| stmt)
.collect(); .collect();
// Remove all raw expression statements that are pure except for the very last statement // Remove all raw expression statements that are pure except for the very last statement
let last_stmt = if preserve_result { result.pop() } else { None }; let last_stmt = if preserve_result { result.pop() } else { None };
result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure())); result.retain(|stmt| !stmt.is_pure());
if let Some(stmt) = last_stmt { if let Some(stmt) = last_stmt {
result.push(stmt); result.push(stmt);
@ -197,6 +216,24 @@ fn optimize_stmt<'a>(stmt: Stmt, state: &mut State<'a>, preserve_result: bool) -
.collect(); .collect();
} }
// Remove everything following the the first return/throw
let mut dead_code = false;
result.retain(|stmt| {
if dead_code {
return false;
}
match stmt {
Stmt::ReturnWithVal(_, _, _) | Stmt::Break(_) => {
dead_code = true;
}
_ => (),
}
true
});
if orig_len != result.len() { if orig_len != result.len() {
state.set_dirty(); state.set_dirty();
} }
@ -504,7 +541,15 @@ pub fn optimize_ast(
OptimizationLevel::Simple | OptimizationLevel::Full => { OptimizationLevel::Simple | OptimizationLevel::Full => {
let pos = fn_def.body.position(); let pos = fn_def.body.position();
let mut body = optimize(vec![fn_def.body], None, &Scope::new()); let mut body = optimize(vec![fn_def.body], None, &Scope::new());
fn_def.body = body.pop().unwrap_or_else(|| Stmt::Noop(pos)); fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) {
Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => {
Stmt::Expr(val)
}
Stmt::ReturnWithVal(None, ReturnType::Return, pos) => {
Stmt::Expr(Box::new(Expr::Unit(pos)))
}
stmt => stmt,
};
} }
} }
Arc::new(fn_def) Arc::new(fn_def)

View File

@ -201,6 +201,19 @@ pub enum Stmt {
} }
impl Stmt { impl Stmt {
pub fn position(&self) -> Position {
match self {
Stmt::Noop(pos)
| Stmt::Let(_, _, pos)
| Stmt::Const(_, _, pos)
| Stmt::Block(_, pos)
| Stmt::Break(pos)
| Stmt::ReturnWithVal(_, _, pos) => *pos,
Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(),
Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(),
}
}
pub fn is_noop(&self) -> bool { pub fn is_noop(&self) -> bool {
matches!(self, Stmt::Noop(_)) matches!(self, Stmt::Noop(_))
} }
@ -230,16 +243,21 @@ impl Stmt {
} }
} }
pub fn position(&self) -> Position { pub fn is_pure(&self) -> bool {
match self { match self {
Stmt::Noop(pos) Stmt::Noop(_) => true,
| Stmt::Let(_, _, pos) Stmt::Expr(expr) => expr.is_pure(),
| Stmt::Const(_, _, pos) Stmt::IfElse(guard, if_block, Some(else_block)) => {
| Stmt::Block(_, pos) guard.is_pure() && if_block.is_pure() && else_block.is_pure()
| Stmt::Break(pos) }
| Stmt::ReturnWithVal(_, _, pos) => *pos, Stmt::IfElse(guard, block, None) | Stmt::While(guard, block) => {
Stmt::IfElse(expr, _, _) | Stmt::Expr(expr) => expr.position(), guard.is_pure() && block.is_pure()
Stmt::While(_, stmt) | Stmt::Loop(stmt) | Stmt::For(_, _, stmt) => stmt.position(), }
Stmt::Loop(block) => block.is_pure(),
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,
} }
} }
} }
@ -353,6 +371,8 @@ impl Expr {
Expr::And(x, y) | Expr::Or(x, y) => x.is_pure() && y.is_pure(), Expr::And(x, y) | Expr::Or(x, y) => x.is_pure() && y.is_pure(),
Expr::Stmt(stmt, _) => stmt.is_pure(),
expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)), expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)),
} }
} }
@ -1457,7 +1477,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
match input.peek() { match input.peek() {
Some((Token::LeftBrace, pos)) => { Some((Token::LeftBrace, pos)) => {
let pos = *pos; let pos = *pos;
return parse_block(input).map(|block| Expr::Stmt(Box::new(block), pos)); return parse_block(input, false).map(|block| Expr::Stmt(Box::new(block), pos));
} }
_ => (), _ => (),
} }
@ -1467,8 +1487,9 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
let mut can_be_indexed = false; let mut can_be_indexed = false;
#[allow(unused_mut)] #[allow(unused_mut)]
let mut root_expr = let mut root_expr = match token
match token.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? { .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
{
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
(Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)),
@ -1493,7 +1514,7 @@ fn parse_primary<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Pa
} }
(Token::True, pos) => Ok(Expr::True(pos)), (Token::True, pos) => Ok(Expr::True(pos)),
(Token::False, pos) => Ok(Expr::False(pos)), (Token::False, pos) => Ok(Expr::False(pos)),
(Token::LexError(le), pos) => Err(ParseError::new(PERR::BadInput(le.to_string()), pos)), (Token::LexError(err), pos) => Err(ParseError::new(PERR::BadInput(err.to_string()), pos)),
(token, pos) => Err(ParseError::new( (token, pos) => Err(ParseError::new(
PERR::BadInput(format!("Unexpected '{}'", token.syntax())), PERR::BadInput(format!("Unexpected '{}'", token.syntax())),
pos, pos,
@ -1814,19 +1835,22 @@ fn parse_expr<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Expr, Parse
parse_binary_op(input, 1, lhs) parse_binary_op(input, 1, lhs)
} }
fn parse_if<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_if<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
) -> Result<Stmt, ParseError> {
input.next(); input.next();
let guard = parse_expr(input)?; let guard = parse_expr(input)?;
let if_body = parse_block(input)?; let if_body = parse_block(input, breakable)?;
let else_body = if matches!(input.peek(), Some((Token::Else, _))) { let else_body = if matches!(input.peek(), Some((Token::Else, _))) {
input.next(); input.next();
Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) { Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) {
parse_if(input)? parse_if(input, breakable)?
} else { } else {
parse_block(input)? parse_block(input, breakable)?
})) }))
} else { } else {
None None
@ -1839,7 +1863,7 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
input.next(); input.next();
let guard = parse_expr(input)?; let guard = parse_expr(input)?;
let body = parse_block(input)?; let body = parse_block(input, true)?;
Ok(Stmt::While(Box::new(guard), Box::new(body))) Ok(Stmt::While(Box::new(guard), Box::new(body)))
} }
@ -1847,7 +1871,7 @@ fn parse_while<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_loop<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> {
input.next(); input.next();
let body = parse_block(input)?; let body = parse_block(input, true)?;
Ok(Stmt::Loop(Box::new(body))) Ok(Stmt::Loop(Box::new(body)))
} }
@ -1860,8 +1884,8 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))? .ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
{ {
(Token::Identifier(s), _) => s, (Token::Identifier(s), _) => s,
(Token::LexError(s), pos) => { (Token::LexError(err), pos) => {
return Err(ParseError::new(PERR::BadInput(s.to_string()), pos)) return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
} }
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
}; };
@ -1875,7 +1899,7 @@ fn parse_for<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseE
} }
let expr = parse_expr(input)?; let expr = parse_expr(input)?;
let body = parse_block(input)?; let body = parse_block(input, true)?;
Ok(Stmt::For(name, Box::new(expr), Box::new(body))) Ok(Stmt::For(name, Box::new(expr), Box::new(body)))
} }
@ -1894,8 +1918,8 @@ fn parse_var<'a>(
.ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))? .ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))?
{ {
(Token::Identifier(s), _) => s, (Token::Identifier(s), _) => s,
(Token::LexError(s), pos) => { (Token::LexError(err), pos) => {
return Err(ParseError::new(PERR::BadInput(s.to_string()), pos)) return Err(ParseError::new(PERR::BadInput(err.to_string()), pos))
} }
(_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)),
}; };
@ -1921,7 +1945,10 @@ fn parse_var<'a>(
} }
} }
fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_block<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
) -> Result<Stmt, ParseError> {
let pos = match input let pos = match input
.next() .next()
.ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))? .ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))?
@ -1932,30 +1959,20 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
let mut statements = Vec::new(); let mut statements = Vec::new();
match input.peek().ok_or_else(|| { while !matches!(input.peek(), Some((Token::RightBrace, _))) {
ParseError::new(
PERR::MissingRightBrace("end of block".into()),
Position::eof(),
)
})? {
(Token::RightBrace, _) => (), // empty block
_ => {
while input.peek().is_some() {
// Parse statements inside the block // Parse statements inside the block
let stmt = parse_stmt(input)?; let stmt = parse_stmt(input, breakable)?;
// 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();
statements.push(stmt); statements.push(stmt);
// End block with right brace
if let Some((Token::RightBrace, _)) = input.peek() {
break;
}
match input.peek() { match input.peek() {
None => break,
Some((Token::RightBrace, _)) => break,
Some((Token::SemiColon, _)) => { Some((Token::SemiColon, _)) => {
input.next(); input.next();
} }
@ -1968,15 +1985,6 @@ fn parse_block<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Pars
*pos, *pos,
)); ));
} }
None => {
return Err(ParseError::new(
PERR::MissingRightBrace("end of block".into()),
Position::eof(),
))
}
}
}
} }
} }
@ -2001,7 +2009,10 @@ fn parse_expr_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt,
Ok(Stmt::Expr(Box::new(parse_expr(input)?))) Ok(Stmt::Expr(Box::new(parse_expr(input)?)))
} }
fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, ParseError> { fn parse_stmt<'a>(
input: &mut Peekable<TokenIterator<'a>>,
breakable: bool,
) -> Result<Stmt, ParseError> {
match input match input
.peek() .peek()
.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))?
@ -2009,15 +2020,16 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
(Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)), (Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)),
(Token::If, _) => parse_if(input), (Token::If, _) => parse_if(input, breakable),
(Token::While, _) => parse_while(input), (Token::While, _) => parse_while(input),
(Token::Loop, _) => parse_loop(input), (Token::Loop, _) => parse_loop(input),
(Token::For, _) => parse_for(input), (Token::For, _) => parse_for(input),
(Token::Break, pos) => { (Token::Break, pos) if breakable => {
let pos = *pos; let pos = *pos;
input.next(); input.next();
Ok(Stmt::Break(pos)) Ok(Stmt::Break(pos))
} }
(Token::Break, pos) => return Err(ParseError::new(PERR::LoopBreak, *pos)),
(token @ Token::Return, _) | (token @ Token::Throw, _) => { (token @ Token::Return, _) | (token @ Token::Throw, _) => {
let return_type = match token { let return_type = match token {
Token::Return => ReturnType::Return, Token::Return => ReturnType::Return,
@ -2038,12 +2050,15 @@ fn parse_stmt<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<Stmt, Parse
// return or throw with expression // return or throw with expression
Some((_, pos)) => { Some((_, pos)) => {
let pos = *pos; let pos = *pos;
let ret = parse_expr(input)?; Ok(Stmt::ReturnWithVal(
Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), return_type, pos)) Some(Box::new(parse_expr(input)?)),
return_type,
pos,
))
} }
} }
} }
(Token::LeftBrace, _) => parse_block(input), (Token::LeftBrace, _) => parse_block(input, breakable),
(Token::Let, _) => parse_var(input, VariableType::Normal), (Token::Let, _) => parse_var(input, VariableType::Normal),
(Token::Const, _) => parse_var(input, VariableType::Constant), (Token::Const, _) => parse_var(input, VariableType::Constant),
_ => parse_expr_stmt(input), _ => parse_expr_stmt(input),
@ -2137,7 +2152,11 @@ fn parse_fn<'a>(input: &mut Peekable<TokenIterator<'a>>) -> Result<FnDef, ParseE
} }
} }
let body = parse_block(input)?; let body = match input.peek() {
Some((Token::LeftBrace, _)) => parse_block(input, false)?,
Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingBody(name), *pos)),
None => return Err(ParseError::new(PERR::FnMissingBody(name), Position::eof())),
};
Ok(FnDef { Ok(FnDef {
name, name,
@ -2169,7 +2188,7 @@ fn parse_global_level<'a, 'e>(
} }
} }
let stmt = parse_stmt(input)?; let stmt = parse_stmt(input, false)?;
let need_semicolon = !stmt.is_self_terminated(); let need_semicolon = !stmt.is_self_terminated();

View File

@ -62,8 +62,8 @@ pub enum EvalAltResult {
ErrorArithmetic(String, Position), ErrorArithmetic(String, Position),
/// Run-time error encountered. Wrapped value is the error message. /// Run-time error encountered. Wrapped value is the error message.
ErrorRuntime(String, Position), ErrorRuntime(String, Position),
/// Internal use: Breaking out of loops. /// Breaking out of loops - not an error if within a loop.
LoopBreak, ErrorLoopBreak(Position),
/// Not an error: Value returned from a script via the `return` keyword. /// Not an error: Value returned from a script via the `return` keyword.
/// Wrapped value is the result value. /// Wrapped value is the result value.
Return(Dynamic, Position), Return(Dynamic, Position),
@ -106,7 +106,7 @@ impl EvalAltResult {
Self::ErrorDotExpr(_, _) => "Malformed dot expression", Self::ErrorDotExpr(_, _) => "Malformed dot expression",
Self::ErrorArithmetic(_, _) => "Arithmetic error", Self::ErrorArithmetic(_, _) => "Arithmetic error",
Self::ErrorRuntime(_, _) => "Runtime error", Self::ErrorRuntime(_, _) => "Runtime error",
Self::LoopBreak => "[Not Error] Breaks out of loop", Self::ErrorLoopBreak(_) => "Break statement not inside a loop",
Self::Return(_, _) => "[Not Error] Function returns value", Self::Return(_, _) => "[Not Error] Function returns value",
} }
} }
@ -134,7 +134,7 @@ impl fmt::Display for EvalAltResult {
Self::ErrorRuntime(s, pos) => { Self::ErrorRuntime(s, pos) => {
write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos) write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos)
} }
Self::LoopBreak => write!(f, "{}", desc), Self::ErrorLoopBreak(pos) => write!(f, "{} ({})", desc, pos),
Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), Self::Return(_, pos) => write!(f, "{} ({})", desc, pos),
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
Self::ErrorReadingScriptFile(path, err) => { Self::ErrorReadingScriptFile(path, err) => {
@ -211,7 +211,6 @@ impl EvalAltResult {
match self { match self {
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
Self::ErrorReadingScriptFile(_, _) => Position::none(), Self::ErrorReadingScriptFile(_, _) => Position::none(),
Self::LoopBreak => Position::none(),
Self::ErrorParsing(err) => err.position(), Self::ErrorParsing(err) => err.position(),
@ -232,6 +231,7 @@ impl EvalAltResult {
| Self::ErrorDotExpr(_, pos) | Self::ErrorDotExpr(_, pos)
| Self::ErrorArithmetic(_, pos) | Self::ErrorArithmetic(_, pos)
| Self::ErrorRuntime(_, pos) | Self::ErrorRuntime(_, pos)
| Self::ErrorLoopBreak(pos)
| Self::Return(_, pos) => *pos, | Self::Return(_, pos) => *pos,
} }
} }
@ -240,7 +240,6 @@ impl EvalAltResult {
match self { match self {
#[cfg(not(feature = "no_stdlib"))] #[cfg(not(feature = "no_stdlib"))]
Self::ErrorReadingScriptFile(_, _) => (), Self::ErrorReadingScriptFile(_, _) => (),
Self::LoopBreak => (),
Self::ErrorParsing(ParseError(_, ref mut pos)) Self::ErrorParsing(ParseError(_, ref mut pos))
| Self::ErrorFunctionNotFound(_, ref mut pos) | Self::ErrorFunctionNotFound(_, ref mut pos)
@ -260,6 +259,7 @@ impl EvalAltResult {
| Self::ErrorDotExpr(_, ref mut pos) | Self::ErrorDotExpr(_, ref mut pos)
| Self::ErrorArithmetic(_, ref mut pos) | Self::ErrorArithmetic(_, ref mut pos)
| Self::ErrorRuntime(_, ref mut pos) | Self::ErrorRuntime(_, ref mut pos)
| Self::ErrorLoopBreak(ref mut pos)
| Self::Return(_, ref mut pos) => *pos = new_position, | Self::Return(_, ref mut pos) => *pos = new_position,
} }
} }

View File

@ -1,10 +1,11 @@
use rhai::{Engine, EvalAltResult}; use rhai::{Engine, EvalAltResult, INT};
#[test] #[test]
fn test_loop() -> Result<(), EvalAltResult> { fn test_loop() -> Result<(), EvalAltResult> {
let mut engine = Engine::new(); let mut engine = Engine::new();
assert!(engine.eval::<bool>( assert_eq!(
engine.eval::<INT>(
r" r"
let x = 0; let x = 0;
let i = 0; let i = 0;
@ -13,15 +14,16 @@ fn test_loop() -> Result<(), EvalAltResult> {
if i < 10 { if i < 10 {
x = x + i; x = x + i;
i = i + 1; i = i + 1;
} } else {
else {
break; break;
} }
} }
x == 45 return x;
" "
)?); )?,
45
);
Ok(()) Ok(())
} }

View File

@ -9,6 +9,6 @@ fn test_throw() {
EvalAltResult::ErrorRuntime(s, _) if s == "hello")); EvalAltResult::ErrorRuntime(s, _) if s == "hello"));
assert!(matches!( assert!(matches!(
engine.eval::<()>(r#"throw;"#).expect_err("expects error"), engine.eval::<()>(r#"throw"#).expect_err("expects error"),
EvalAltResult::ErrorRuntime(s, _) if s == "")); EvalAltResult::ErrorRuntime(s, _) if s == ""));
} }