Handle break and return better.
This commit is contained in:
parent
d2951bfb6b
commit
8efe080412
@ -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!("");
|
||||||
|
@ -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
|
||||||
|
@ -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) => {
|
||||||
|
15
src/error.rs
15
src/error.rs
@ -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)?
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
217
src/parser.rs
217
src/parser.rs
@ -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,38 +1487,39 @@ 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"))]
|
{
|
||||||
(Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)),
|
#[cfg(not(feature = "no_float"))]
|
||||||
|
(Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)),
|
||||||
|
|
||||||
(Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)),
|
(Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)),
|
||||||
(Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)),
|
(Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)),
|
||||||
(Token::StringConst(s), pos) => {
|
(Token::StringConst(s), pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
Ok(Expr::StringConstant(s, pos))
|
Ok(Expr::StringConstant(s, pos))
|
||||||
}
|
}
|
||||||
(Token::Identifier(s), pos) => {
|
(Token::Identifier(s), pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_ident_expr(s, input, pos)
|
parse_ident_expr(s, input, pos)
|
||||||
}
|
}
|
||||||
(Token::LeftParen, pos) => {
|
(Token::LeftParen, pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_paren_expr(input, pos)
|
parse_paren_expr(input, pos)
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
(Token::LeftBracket, pos) => {
|
(Token::LeftBracket, pos) => {
|
||||||
can_be_indexed = true;
|
can_be_indexed = true;
|
||||||
parse_array_expr(input, pos)
|
parse_array_expr(input, pos)
|
||||||
}
|
}
|
||||||
(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,
|
||||||
)),
|
)),
|
||||||
}?;
|
}?;
|
||||||
|
|
||||||
if can_be_indexed {
|
if can_be_indexed {
|
||||||
// Tail processing all possible indexing
|
// Tail processing all possible indexing
|
||||||
@ -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,50 +1959,31 @@ 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(
|
// Parse statements inside the block
|
||||||
PERR::MissingRightBrace("end of block".into()),
|
let stmt = parse_stmt(input, breakable)?;
|
||||||
Position::eof(),
|
|
||||||
)
|
|
||||||
})? {
|
|
||||||
(Token::RightBrace, _) => (), // empty block
|
|
||||||
|
|
||||||
_ => {
|
// See if it needs a terminating semicolon
|
||||||
while input.peek().is_some() {
|
let need_semicolon = !stmt.is_self_terminated();
|
||||||
// Parse statements inside the block
|
|
||||||
let stmt = parse_stmt(input)?;
|
|
||||||
|
|
||||||
// See if it needs a terminating semicolon
|
statements.push(stmt);
|
||||||
let need_semicolon = !stmt.is_self_terminated();
|
|
||||||
|
|
||||||
statements.push(stmt);
|
match input.peek() {
|
||||||
|
None => break,
|
||||||
|
|
||||||
// End block with right brace
|
Some((Token::RightBrace, _)) => break,
|
||||||
if let Some((Token::RightBrace, _)) = input.peek() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
match input.peek() {
|
Some((Token::SemiColon, _)) => {
|
||||||
Some((Token::SemiColon, _)) => {
|
input.next();
|
||||||
input.next();
|
}
|
||||||
}
|
Some((_, _)) if !need_semicolon => (),
|
||||||
Some((_, _)) if !need_semicolon => (),
|
|
||||||
|
|
||||||
Some((_, pos)) => {
|
Some((_, pos)) => {
|
||||||
// Semicolons are not optional between statements
|
// Semicolons are not optional between statements
|
||||||
return Err(ParseError::new(
|
return Err(ParseError::new(
|
||||||
PERR::MissingSemicolon("terminating a statement".into()),
|
PERR::MissingSemicolon("terminating a statement".into()),
|
||||||
*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();
|
||||||
|
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,27 +1,29 @@
|
|||||||
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!(
|
||||||
r"
|
engine.eval::<INT>(
|
||||||
let x = 0;
|
r"
|
||||||
let i = 0;
|
let x = 0;
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if i < 10 {
|
if i < 10 {
|
||||||
x = x + i;
|
x = x + i;
|
||||||
i = i + 1;
|
i = i + 1;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
x == 45
|
return x;
|
||||||
"
|
"
|
||||||
)?);
|
)?,
|
||||||
|
45
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -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 == ""));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user