Catch more parse errors.

This commit is contained in:
Stephen Chung 2021-07-04 00:15:27 +08:00
parent 69c14e65f3
commit b4da054bab
4 changed files with 72 additions and 20 deletions

View File

@ -13,6 +13,7 @@ Bug fixes
* Fixed infinite loop in certain script optimizations. * Fixed infinite loop in certain script optimizations.
* Building for `no-std` no longer requires patching `smartstring`. * Building for `no-std` no longer requires patching `smartstring`.
* Parsing a lone `return` or `throw` without a semicolon at the end of a block no longer raises an error.
Breaking changes Breaking changes
---------------- ----------------

View File

@ -125,6 +125,9 @@ pub enum ParseErrorType {
VariableExpected, VariableExpected,
/// An identifier is a reserved keyword. /// An identifier is a reserved keyword.
Reserved(String), Reserved(String),
/// An expression is of the wrong type.
/// Wrapped values are the type requested and type of the actual result.
MismatchedType(String, String),
/// Missing an expression. Wrapped value is the expression type. /// Missing an expression. Wrapped value is the expression type.
ExprExpected(String), ExprExpected(String),
/// Defining a doc-comment in an appropriate place (e.g. not at global level). /// Defining a doc-comment in an appropriate place (e.g. not at global level).
@ -230,6 +233,7 @@ impl fmt::Display for ParseErrorType {
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"), Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s), Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name '{}'", s),
Self::MismatchedType(r, a) => write!(f, "Expecting {}, not {}", r, a),
Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), Self::ExprExpected(s) => write!(f, "Expecting {} expression", s),
Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s),
Self::MissingSymbol(s) => f.write_str(s), Self::MissingSymbol(s) => f.write_str(s),

View File

@ -281,6 +281,47 @@ impl Expr {
_ => self, _ => self,
} }
} }
/// Raise an error if the expression can never yield a boolean value.
fn ensure_bool_expr(self) -> Result<Expr, ParseError> {
let type_name = match self {
Expr::Unit(_) => "()",
Expr::DynamicConstant(ref v, _) if !v.is::<bool>() => v.type_name(),
Expr::IntegerConstant(_, _) => "a number",
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, _) => "a floating-point number",
Expr::CharConstant(_, _) => "a character",
Expr::StringConstant(_, _) => "a string",
Expr::InterpolatedString(_, _) => "a string",
Expr::Array(_, _) => "an array",
Expr::Map(_, _) => "an object map",
_ => return Ok(self),
};
Err(
PERR::MismatchedType("a boolean expression".to_string(), type_name.to_string())
.into_err(self.position()),
)
}
/// Raise an error if the expression can never yield an iterable value.
fn ensure_iterable(self) -> Result<Expr, ParseError> {
let type_name = match self {
Expr::Unit(_) => "()",
Expr::BoolConstant(_, _) => "a boolean",
Expr::IntegerConstant(_, _) => "a number",
#[cfg(not(feature = "no_float"))]
Expr::FloatConstant(_, _) => "a floating-point number",
Expr::CharConstant(_, _) => "a character",
Expr::StringConstant(_, _) => "a string",
Expr::InterpolatedString(_, _) => "a string",
Expr::Map(_, _) => "an object map",
_ => return Ok(self),
};
Err(
PERR::MismatchedType("an iterable value".to_string(), type_name.to_string())
.into_err(self.position()),
)
}
} }
/// Consume a particular [token][Token], checking that it is the expected one. /// Consume a particular [token][Token], checking that it is the expected one.
@ -1816,8 +1857,8 @@ fn parse_binary_op(
.expect("never fails because `||` has two arguments"); .expect("never fails because `||` has two arguments");
Expr::Or( Expr::Or(
BinaryExpr { BinaryExpr {
lhs: current_lhs, lhs: current_lhs.ensure_bool_expr()?,
rhs, rhs: rhs.ensure_bool_expr()?,
} }
.into(), .into(),
pos, pos,
@ -1832,8 +1873,8 @@ fn parse_binary_op(
.expect("never fails because `&&` has two arguments"); .expect("never fails because `&&` has two arguments");
Expr::And( Expr::And(
BinaryExpr { BinaryExpr {
lhs: current_lhs, lhs: current_lhs.ensure_bool_expr()?,
rhs, rhs: rhs.ensure_bool_expr()?,
} }
.into(), .into(),
pos, pos,
@ -1896,10 +1937,9 @@ fn parse_custom_syntax(
let mut tokens: StaticVec<_> = Default::default(); let mut tokens: StaticVec<_> = Default::default();
// Adjust the variables stack // Adjust the variables stack
if syntax.scope_changed { if syntax.scope_may_be_changed {
// Add an empty variable name to the stack. // Add a barrier variable to the stack so earlier variables will not be matched.
// Empty variable names act as a barrier so earlier variables will not be matched. // Variable searches stop at the first barrier.
// Variable searches stop at the first empty variable name.
let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER); let empty = state.get_identifier(SCOPE_SEARCH_BARRIER_MARKER);
state.stack.push((empty, AccessMode::ReadWrite)); state.stack.push((empty, AccessMode::ReadWrite));
} }
@ -2017,12 +2057,15 @@ fn parse_custom_syntax(
keywords.shrink_to_fit(); keywords.shrink_to_fit();
tokens.shrink_to_fit(); tokens.shrink_to_fit();
Ok(CustomExpr { Ok(Expr::Custom(
keywords, CustomExpr {
tokens, keywords,
scope_changed: syntax.scope_changed, tokens,
} scope_may_be_changed: syntax.scope_may_be_changed,
.into_custom_syntax_expr(pos)) }
.into(),
pos,
))
} }
/// Parse an expression. /// Parse an expression.
@ -2125,7 +2168,7 @@ fn parse_if(
// 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, lib, settings.level_up())?; let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
let if_body = parse_block(input, state, lib, settings.level_up())?; let if_body = parse_block(input, state, lib, settings.level_up())?;
@ -2163,7 +2206,7 @@ fn parse_while_loop(
let (guard, token_pos) = match input.next().expect(NEVER_ENDS) { let (guard, token_pos) = match input.next().expect(NEVER_ENDS) {
(Token::While, pos) => { (Token::While, pos) => {
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
let expr = parse_expr(input, state, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
(expr, pos) (expr, pos)
} }
(Token::Loop, pos) => (Expr::Unit(Position::NONE), pos), (Token::Loop, pos) => (Expr::Unit(Position::NONE), pos),
@ -2208,7 +2251,7 @@ fn parse_do(
ensure_not_statement_expr(input, "a boolean")?; ensure_not_statement_expr(input, "a boolean")?;
settings.is_breakable = false; settings.is_breakable = false;
let guard = parse_expr(input, state, lib, settings.level_up())?; let guard = parse_expr(input, state, lib, settings.level_up())?.ensure_bool_expr()?;
ensure_not_assignment(input)?; ensure_not_assignment(input)?;
Ok(Stmt::Do( Ok(Stmt::Do(
@ -2279,7 +2322,7 @@ fn parse_for(
// 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, lib, settings.level_up())?; let expr = parse_expr(input, state, lib, settings.level_up())?.ensure_iterable()?;
let prev_stack_len = state.stack.len(); let prev_stack_len = state.stack.len();
@ -2758,6 +2801,10 @@ fn parse_stmt(
match input.peek().expect(NEVER_ENDS) { match input.peek().expect(NEVER_ENDS) {
// `return`/`throw` at <EOF> // `return`/`throw` at <EOF>
(Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)), (Token::EOF, _) => Ok(Stmt::Return(return_type, None, token_pos)),
// `return`/`throw` at end of block
(Token::RightBrace, _) if !settings.is_global => {
Ok(Stmt::Return(return_type, None, token_pos))
}
// `return;` or `throw;` // `return;` or `throw;`
(Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)), (Token::SemiColon, _) => Ok(Stmt::Return(return_type, None, token_pos)),
// `return` or `throw` with expression // `return` or `throw` with expression

View File

@ -25,9 +25,9 @@ fn test_bool_op3() -> Result<(), Box<EvalAltResult>> {
let engine = Engine::new(); let engine = Engine::new();
assert!(engine.eval::<bool>("true && (false || 123)").is_err()); assert!(engine.eval::<bool>("true && (false || 123)").is_err());
assert_eq!(engine.eval::<bool>("true && (true || 123)")?, true); assert_eq!(engine.eval::<bool>("true && (true || { throw })")?, true);
assert!(engine.eval::<bool>("123 && (false || true)").is_err()); assert!(engine.eval::<bool>("123 && (false || true)").is_err());
assert_eq!(engine.eval::<bool>("false && (true || 123)")?, false); assert_eq!(engine.eval::<bool>("false && (true || { throw })")?, false);
Ok(()) Ok(())
} }