Catch more parse errors.
This commit is contained in:
parent
69c14e65f3
commit
b4da054bab
@ -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
|
||||||
----------------
|
----------------
|
||||||
|
@ -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),
|
||||||
|
83
src/parse.rs
83
src/parse.rs
@ -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
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user