diff --git a/RELEASES.md b/RELEASES.md index 011c5c80..0b3136c1 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -13,6 +13,7 @@ Breaking changes * Callback closure passed to `Engine::on_progress` now takes `&u64` instead of `u64` to be consistent with other callback signatures. * `Engine::register_indexer` is renamed to `Engine::register_indexer_get`. * `Module::set_indexer_fn` is renamed to `Module::set_indexer_get_fn`. +* The tuple `ParseError` now exposes the internal fields and the `ParseError::error_type` and `ParseError::position` methods are removed. The first tuple field is the `ParseErrorType` and the second tuple field is the `Position`. New features ------------ diff --git a/examples/repl.rs b/examples/repl.rs index f980a98e..4a3bd561 100644 --- a/examples/repl.rs +++ b/examples/repl.rs @@ -7,43 +7,41 @@ use std::io::{stdin, stdout, Write}; fn print_error(input: &str, err: EvalAltResult) { let lines: Vec<_> = input.trim().split('\n').collect(); + let pos = err.position(); let line_no = if lines.len() > 1 { - match err.position() { - p if p.is_none() => "".to_string(), - p => format!("{}: ", p.line().unwrap()), + if pos.is_none() { + "".to_string() + } else { + format!("{}: ", pos.line().unwrap()) } } else { "".to_string() }; // Print error - let pos = err.position(); let pos_text = format!(" ({})", pos); - match pos { - p if p.is_none() => { - // No position - println!("{}", err); - } - p => { - // Specific position - println!("{}{}", line_no, lines[p.line().unwrap() - 1]); + if pos.is_none() { + // No position + println!("{}", err); + } else { + // Specific position + println!("{}{}", line_no, lines[pos.line().unwrap() - 1]); - let err_text = match err { - EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { - format!("Runtime error: {}", err) - } - err => err.to_string(), - }; + let err_text = match err { + EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { + format!("Runtime error: {}", err) + } + err => err.to_string(), + }; - println!( - "{0:>1$} {2}", - "^", - line_no.len() + p.position().unwrap(), - err_text.replace(&pos_text, "") - ); - } + println!( + "{0:>1$} {2}", + "^", + line_no.len() + pos.position().unwrap(), + err_text.replace(&pos_text, "") + ); } } diff --git a/examples/rhai_runner.rs b/examples/rhai_runner.rs index c5c5128d..135269ce 100644 --- a/examples/rhai_runner.rs +++ b/examples/rhai_runner.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult}; +use rhai::{Engine, EvalAltResult, Position}; #[cfg(not(feature = "no_optimize"))] use rhai::OptimizationLevel; @@ -6,15 +6,17 @@ use rhai::OptimizationLevel; use std::{env, fs::File, io::Read, process::exit}; fn eprint_error(input: &str, err: EvalAltResult) { - fn eprint_line(lines: &[&str], line: usize, pos: usize, err: &str) { + fn eprint_line(lines: &[&str], pos: Position, err: &str) { + let line = pos.line().unwrap(); + let line_no = format!("{}: ", line); - let pos_text = format!(" (line {}, position {})", line, pos); + let pos_text = format!(" ({})", pos); eprintln!("{}{}", line_no, lines[line - 1]); eprintln!( "{:>1$} {2}", "^", - line_no.len() + pos, + line_no.len() + pos.position().unwrap(), err.replace(&pos_text, "") ); eprintln!(""); @@ -25,22 +27,19 @@ fn eprint_error(input: &str, err: EvalAltResult) { // Print error let pos = err.position(); - match pos { - p if p.is_none() => { - // No position - eprintln!("{}", err); - } - p => { - // Specific position - let err_text = match err { - EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { - format!("Runtime error: {}", err) - } - err => err.to_string(), - }; + if pos.is_none() { + // No position + eprintln!("{}", err); + } else { + // Specific position + let err_text = match err { + EvalAltResult::ErrorRuntime(err, _) if !err.is_empty() => { + format!("Runtime error: {}", err) + } + err => err.to_string(), + }; - eprint_line(&lines, p.line().unwrap(), p.position().unwrap(), &err_text) - } + eprint_line(&lines, pos, &err_text) } } diff --git a/src/engine.rs b/src/engine.rs index 43d32b42..5eb4dc89 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -871,9 +871,7 @@ impl Engine { // If new functions are defined within the eval string, it is an error if ast.lib().num_fn() != 0 { - return Err(Box::new(EvalAltResult::ErrorParsing( - ParseErrorType::WrongFnDefinition.into_err(Position::none()), - ))); + return Err(ParseErrorType::WrongFnDefinition.into()); } let statements = mem::take(ast.statements_mut()); diff --git a/src/error.rs b/src/error.rs index 7bc60a1c..d75af495 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,5 +1,6 @@ //! Module containing error definitions for the parsing process. +use crate::result::EvalAltResult; use crate::token::Position; use crate::stdlib::{boxed::Box, char, error::Error, fmt, string::String}; @@ -123,113 +124,111 @@ impl ParseErrorType { pub(crate) fn into_err(self, pos: Position) -> ParseError { ParseError(Box::new(self), pos) } + + pub(crate) fn desc(&self) -> &str { + match self { + Self::BadInput(p) => p, + Self::UnexpectedEOF => "Script is incomplete", + Self::UnknownOperator(_) => "Unknown operator", + Self::MissingToken(_, _) => "Expecting a certain token that is missing", + Self::MalformedCallExpr(_) => "Invalid expression in function call arguments", + Self::MalformedIndexExpr(_) => "Invalid index in indexing expression", + Self::MalformedInExpr(_) => "Invalid 'in' expression", + Self::DuplicatedProperty(_) => "Duplicated property in object map literal", + Self::ForbiddenConstantExpr(_) => "Expecting a constant", + Self::PropertyExpected => "Expecting name of a property", + Self::VariableExpected => "Expecting name of a variable", + Self::ExprExpected(_) => "Expecting an expression", + Self::FnMissingName => "Expecting name in function declaration", + Self::FnMissingParams(_) => "Expecting parameters in function declaration", + Self::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", + Self::FnMissingBody(_) => "Expecting body statement block for function declaration", + Self::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", + Self::DuplicatedExport(_) => "Duplicated variable/function in export statement", + Self::WrongExport => "Export statement can only appear at global level", + Self::AssignmentToCopy => "Only a copy of the value is change with this assignment", + Self::AssignmentToConstant(_) => "Cannot assign to a constant value", + Self::ExprTooDeep => "Expression exceeds maximum complexity", + Self::LoopBreak => "Break statement should only be used inside a loop" + } + } +} + +impl fmt::Display for ParseErrorType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { + write!(f, "{}", if s.is_empty() { self.desc() } else { s }) + } + Self::ForbiddenConstantExpr(s) => { + write!(f, "Expecting a constant to assign to '{}'", s) + } + Self::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s), + + Self::MalformedIndexExpr(s) => { + write!(f, "{}", if s.is_empty() { self.desc() } else { s }) + } + + Self::MalformedInExpr(s) => write!(f, "{}", if s.is_empty() { self.desc() } else { s }), + + Self::DuplicatedProperty(s) => { + write!(f, "Duplicated property '{}' for object map literal", s) + } + + Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), + + Self::FnMissingParams(s) => write!(f, "Expecting parameters for function '{}'", s), + + Self::FnMissingBody(s) => { + write!(f, "Expecting body statement block for function '{}'", s) + } + + Self::FnDuplicatedParam(s, arg) => { + write!(f, "Duplicated parameter '{}' for function '{}'", arg, s) + } + + Self::DuplicatedExport(s) => write!( + f, + "Duplicated variable/function '{}' in export statement", + s + ), + + Self::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s), + + Self::AssignmentToConstant(s) if s.is_empty() => write!(f, "{}", self.desc()), + Self::AssignmentToConstant(s) => write!(f, "Cannot assign to constant '{}'", s), + _ => write!(f, "{}", self.desc()), + } + } } /// Error when parsing a script. #[derive(Debug, Eq, PartialEq, Clone, Hash)] -pub struct ParseError(pub(crate) Box, pub(crate) Position); - -impl ParseError { - /// Get the parse error. - pub fn error_type(&self) -> &ParseErrorType { - &self.0 - } - - /// Get the location in the script of the error. - pub fn position(&self) -> Position { - self.1 - } - - pub(crate) fn desc(&self) -> &str { - match self.0.as_ref() { - ParseErrorType::BadInput(p) => p, - ParseErrorType::UnexpectedEOF => "Script is incomplete", - ParseErrorType::UnknownOperator(_) => "Unknown operator", - ParseErrorType::MissingToken(_, _) => "Expecting a certain token that is missing", - ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", - ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", - ParseErrorType::MalformedInExpr(_) => "Invalid 'in' expression", - ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal", - ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant", - ParseErrorType::PropertyExpected => "Expecting name of a property", - ParseErrorType::VariableExpected => "Expecting name of a variable", - ParseErrorType::ExprExpected(_) => "Expecting an expression", - ParseErrorType::FnMissingName => "Expecting name in function declaration", - ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", - ParseErrorType::FnDuplicatedParam(_,_) => "Duplicated parameters in function declaration", - ParseErrorType::FnMissingBody(_) => "Expecting body statement block for function declaration", - ParseErrorType::WrongFnDefinition => "Function definitions must be at global level and cannot be inside a block or another function", - ParseErrorType::DuplicatedExport(_) => "Duplicated variable/function in export statement", - ParseErrorType::WrongExport => "Export statement can only appear at global level", - ParseErrorType::AssignmentToCopy => "Only a copy of the value is change with this assignment", - ParseErrorType::AssignmentToConstant(_) => "Cannot assign to a constant value", - ParseErrorType::ExprTooDeep => "Expression exceeds maximum complexity", - ParseErrorType::LoopBreak => "Break statement should only be used inside a loop" - } - } -} +pub struct ParseError(pub Box, pub Position); impl Error for ParseError {} impl fmt::Display for ParseError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0.as_ref() { - ParseErrorType::BadInput(s) | ParseErrorType::MalformedCallExpr(s) => { - write!(f, "{}", if s.is_empty() { self.desc() } else { s })? - } - ParseErrorType::ForbiddenConstantExpr(s) => { - write!(f, "Expecting a constant to assign to '{}'", s)? - } - ParseErrorType::UnknownOperator(s) => write!(f, "{}: '{}'", self.desc(), s)?, - - ParseErrorType::MalformedIndexExpr(s) => { - write!(f, "{}", if s.is_empty() { self.desc() } else { s })? - } - - ParseErrorType::MalformedInExpr(s) => { - write!(f, "{}", if s.is_empty() { self.desc() } else { s })? - } - - ParseErrorType::DuplicatedProperty(s) => { - write!(f, "Duplicated property '{}' for object map literal", s)? - } - - ParseErrorType::ExprExpected(s) => write!(f, "Expecting {} expression", s)?, - - ParseErrorType::FnMissingParams(s) => { - write!(f, "Expecting parameters for function '{}'", s)? - } - - ParseErrorType::FnMissingBody(s) => { - write!(f, "Expecting body statement block for function '{}'", s)? - } - - ParseErrorType::FnDuplicatedParam(s, arg) => { - write!(f, "Duplicated parameter '{}' for function '{}'", arg, s)? - } - - ParseErrorType::DuplicatedExport(s) => write!( - f, - "Duplicated variable/function '{}' in export statement", - s - )?, - - ParseErrorType::MissingToken(token, s) => write!(f, "Expecting '{}' {}", token, s)?, - - ParseErrorType::AssignmentToConstant(s) if s.is_empty() => { - write!(f, "{}", self.desc())? - } - ParseErrorType::AssignmentToConstant(s) => { - write!(f, "Cannot assign to constant '{}'", s)? - } - _ => write!(f, "{}", self.desc())?, - } + fmt::Display::fmt(&self.0, f)?; + // Do not write any position if None if !self.1.is_none() { - // Do not write any position if None - Ok(()) - } else { - write!(f, " ({})", self.1) + write!(f, " ({})", self.1)?; } + + Ok(()) + } +} + +impl From for Box { + fn from(err: ParseErrorType) -> Self { + Box::new(EvalAltResult::ErrorParsing(err, Position::none())) + } +} + +impl From for Box { + fn from(err: ParseError) -> Self { + Box::new(EvalAltResult::ErrorParsing(*err.0, err.1)) } } diff --git a/src/parser.rs b/src/parser.rs index 0cd50b57..2eed69b1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -16,7 +16,7 @@ use crate::stdlib::{ char, collections::HashMap, format, - iter::{empty, Peekable}, + iter::empty, mem, num::NonZeroUsize, ops::{Add, Deref, DerefMut}, @@ -751,15 +751,13 @@ fn parse_paren_expr( // ( xxx ) (Token::RightParen, _) => Ok(expr), // ( - (Token::LexError(err), pos) => { - return Err(PERR::BadInput(err.to_string()).into_err(settings.pos)) - } + (Token::LexError(err), pos) => return Err(PERR::BadInput(err.to_string()).into_err(pos)), // ( xxx ??? (_, pos) => Err(PERR::MissingToken( Token::RightParen.into(), "for a matching ( in this expression".into(), ) - .into_err(settings.pos)), + .into_err(pos)), } } @@ -769,10 +767,9 @@ fn parse_call_expr( state: &mut ParseState, id: String, mut modules: Option>, - mut settings: ParseSettings, + settings: ParseSettings, ) -> Result { - let (token, token_pos) = input.peek().unwrap(); - settings.pos = *token_pos; + let (token, _) = input.peek().unwrap(); settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut args = StaticVec::new(); @@ -888,7 +885,7 @@ fn parse_index_chain( input: &mut TokenStream, state: &mut ParseState, lhs: Expr, - settings: ParseSettings, + mut settings: ParseSettings, ) -> Result { settings.ensure_level_within_max_limit(state.max_expr_depth)?; @@ -1031,11 +1028,12 @@ fn parse_index_chain( match input.peek().unwrap() { // If another indexing level, right-bind it (Token::LeftBracket, _) => { - let idx_pos = eat_token(input, Token::LeftBracket); + let prev_pos = settings.pos; + settings.pos = eat_token(input, Token::LeftBracket); // Recursively parse the indexing chain, right-binding each let idx_expr = parse_index_chain(input, state, idx_expr, settings.level_up())?; // Indexing binds to right - Ok(Expr::Index(Box::new((lhs, idx_expr, settings.pos)))) + Ok(Expr::Index(Box::new((lhs, idx_expr, prev_pos)))) } // Otherwise terminate the indexing chain _ => { @@ -1257,11 +1255,13 @@ fn parse_primary( } let (token, token_pos) = input.next().unwrap(); + settings.pos = token_pos; root_expr = match (root_expr, token) { // Function call (Expr::Variable(x), Token::LeftParen) => { let ((name, pos), modules, _, _) = *x; + settings.pos = pos; parse_call_expr(input, state, name, modules, settings.level_up())? } (Expr::Property(_), _) => unreachable!(), @@ -1269,6 +1269,7 @@ fn parse_primary( (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Token::Identifier(id2), pos2) => { let ((name, pos), mut modules, _, index) = *x; + if let Some(ref mut modules) = modules { modules.push((name, pos)); } else { @@ -1284,7 +1285,6 @@ fn parse_primary( // Indexing #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { - settings.pos = token_pos; parse_index_chain(input, state, expr, settings.level_up())? } // Unknown postfix operator @@ -1416,9 +1416,11 @@ fn make_assignment_stmt<'a>( pos: Position, ) -> Result { match &lhs { + // var (non-indexed) = rhs Expr::Variable(x) if x.3.is_none() => { Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) } + // var (indexed) = rhs Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.len() - index.unwrap().get())].1 { @@ -1432,10 +1434,13 @@ fn make_assignment_stmt<'a>( ScopeEntryType::Module => unreachable!(), } } + // xxx[???] = rhs, xxx.??? = rhs Expr::Index(x) | Expr::Dot(x) => match &x.0 { + // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs Expr::Variable(x) if x.3.is_none() => { Ok(Expr::Assignment(Box::new((lhs, fn_name.into(), rhs, pos)))) } + // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { let ((name, name_pos), _, _, index) = x.as_ref(); match state.stack[(state.len() - index.unwrap().get())].1 { @@ -1449,11 +1454,18 @@ fn make_assignment_stmt<'a>( ScopeEntryType::Module => unreachable!(), } } + // expr[???] = rhs, expr.??? = rhs _ => Err(PERR::AssignmentToCopy.into_err(x.0.position())), }, + // const_expr = rhs expr if expr.is_constant() => { Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) } + // ??? && ??? = rhs, ??? || ??? = rhs + Expr::And(_) | Expr::Or(_) => { + Err(PERR::BadInput("Possibly a typo of '=='?".to_string()).into_err(pos)) + } + // expr = rhs _ => Err(PERR::AssignmentToCopy.into_err(lhs.position())), } } diff --git a/src/result.rs b/src/result.rs index 77102085..36bc18f6 100644 --- a/src/result.rs +++ b/src/result.rs @@ -1,7 +1,7 @@ //! Module containing error definitions for the evaluation process. use crate::any::Dynamic; -use crate::error::ParseError; +use crate::error::ParseErrorType; use crate::parser::INT; use crate::token::Position; @@ -23,7 +23,7 @@ use crate::stdlib::path::PathBuf; #[derive(Debug)] pub enum EvalAltResult { /// Syntax error. - ErrorParsing(ParseError), + ErrorParsing(ParseErrorType, Position), /// Error reading from a script file. Wrapped value is the path of the script file. /// @@ -101,7 +101,7 @@ impl EvalAltResult { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, _, _) => "Cannot read from script file", - Self::ErrorParsing(p) => p.desc(), + Self::ErrorParsing(p, _) => p.desc(), Self::ErrorInFunctionCall(_, _, _) => "Error in called function", Self::ErrorFunctionNotFound(_, _) => "Function not found", Self::ErrorBooleanArgMismatch(_, _) => "Boolean operator expects boolean operands", @@ -153,95 +153,89 @@ impl Error for EvalAltResult {} impl fmt::Display for EvalAltResult { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let desc = self.desc(); + let pos = self.position(); match self { #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(path, pos, err) if pos.is_none() => { - write!(f, "{} '{}': {}", desc, path.display(), err) - } - #[cfg(not(feature = "no_std"))] - Self::ErrorReadingScriptFile(path, pos, err) => { - write!(f, "{} '{}': {} ({})", desc, path.display(), err, pos) + Self::ErrorReadingScriptFile(path, _, err) => { + write!(f, "{} '{}': {}", desc, path.display(), err)? } - Self::ErrorParsing(p) => write!(f, "Syntax error: {}", p), + Self::ErrorParsing(p, _) => write!(f, "Syntax error: {}", p)?, - Self::ErrorInFunctionCall(s, err, pos) => { - write!(f, "Error in call to function '{}' ({}): {}", s, pos, err) + Self::ErrorInFunctionCall(s, err, _) => { + write!(f, "Error in call to function '{}' : {}", s, err)? } - Self::ErrorFunctionNotFound(s, pos) - | Self::ErrorVariableNotFound(s, pos) - | Self::ErrorModuleNotFound(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), + Self::ErrorFunctionNotFound(s, _) + | Self::ErrorVariableNotFound(s, _) + | Self::ErrorModuleNotFound(s, _) => write!(f, "{}: '{}'", desc, s)?, - Self::ErrorDotExpr(s, pos) if !s.is_empty() => write!(f, "{} {} ({})", desc, s, pos), + Self::ErrorDotExpr(s, _) if !s.is_empty() => write!(f, "{} {}", desc, s)?, - Self::ErrorIndexingType(_, pos) - | Self::ErrorNumericIndexExpr(pos) - | Self::ErrorStringIndexExpr(pos) - | Self::ErrorImportExpr(pos) - | Self::ErrorLogicGuard(pos) - | Self::ErrorFor(pos) - | Self::ErrorAssignmentToUnknownLHS(pos) - | Self::ErrorInExpr(pos) - | Self::ErrorDotExpr(_, pos) - | Self::ErrorTooManyOperations(pos) - | Self::ErrorTooManyModules(pos) - | Self::ErrorStackOverflow(pos) - | Self::ErrorTerminated(pos) => write!(f, "{} ({})", desc, pos), + Self::ErrorIndexingType(_, _) + | Self::ErrorNumericIndexExpr(_) + | Self::ErrorStringIndexExpr(_) + | Self::ErrorImportExpr(_) + | Self::ErrorLogicGuard(_) + | Self::ErrorFor(_) + | Self::ErrorAssignmentToUnknownLHS(_) + | Self::ErrorInExpr(_) + | Self::ErrorDotExpr(_, _) + | Self::ErrorTooManyOperations(_) + | Self::ErrorTooManyModules(_) + | Self::ErrorStackOverflow(_) + | Self::ErrorTerminated(_) => write!(f, "{}", desc)?, - Self::ErrorRuntime(s, pos) => { - write!(f, "{} ({})", if s.is_empty() { desc } else { s }, pos) + Self::ErrorRuntime(s, _) => write!(f, "{}", if s.is_empty() { desc } else { s })?, + + Self::ErrorAssignmentToConstant(s, _) => write!(f, "{}: '{}'", desc, s)?, + Self::ErrorMismatchOutputType(s, _) => write!(f, "{}: {}", desc, s)?, + Self::ErrorArithmetic(s, _) => write!(f, "{}", s)?, + + Self::ErrorLoopBreak(_, _) => write!(f, "{}", desc)?, + Self::Return(_, _) => write!(f, "{}", desc)?, + + Self::ErrorBooleanArgMismatch(op, _) => { + write!(f, "{} operator expects boolean operands", op)? } - - Self::ErrorAssignmentToConstant(s, pos) => write!(f, "{}: '{}' ({})", desc, s, pos), - Self::ErrorMismatchOutputType(s, pos) => write!(f, "{}: {} ({})", desc, s, pos), - Self::ErrorArithmetic(s, pos) => write!(f, "{} ({})", s, pos), - - Self::ErrorLoopBreak(_, pos) => write!(f, "{} ({})", desc, pos), - Self::Return(_, pos) => write!(f, "{} ({})", desc, pos), - - Self::ErrorBooleanArgMismatch(op, pos) => { - write!(f, "{} operator expects boolean operands ({})", op, pos) + Self::ErrorCharMismatch(_) => write!(f, "string indexing expects a character value")?, + Self::ErrorArrayBounds(_, index, _) if *index < 0 => { + write!(f, "{}: {} < 0", desc, index)? } - Self::ErrorCharMismatch(pos) => { - write!(f, "string indexing expects a character value ({})", pos) - } - Self::ErrorArrayBounds(_, index, pos) if *index < 0 => { - write!(f, "{}: {} < 0 ({})", desc, index, pos) - } - Self::ErrorArrayBounds(0, _, pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorArrayBounds(1, index, pos) => write!( + Self::ErrorArrayBounds(0, _, _) => write!(f, "{}", desc)?, + Self::ErrorArrayBounds(1, index, _) => write!( f, - "Array index {} is out of bounds: only one element in the array ({})", - index, pos - ), - Self::ErrorArrayBounds(max, index, pos) => write!( + "Array index {} is out of bounds: only one element in the array", + index + )?, + Self::ErrorArrayBounds(max, index, _) => write!( f, - "Array index {} is out of bounds: only {} elements in the array ({})", - index, max, pos - ), - Self::ErrorStringBounds(_, index, pos) if *index < 0 => { - write!(f, "{}: {} < 0 ({})", desc, index, pos) + "Array index {} is out of bounds: only {} elements in the array", + index, max + )?, + Self::ErrorStringBounds(_, index, _) if *index < 0 => { + write!(f, "{}: {} < 0", desc, index)? } - Self::ErrorStringBounds(0, _, pos) => write!(f, "{} ({})", desc, pos), - Self::ErrorStringBounds(1, index, pos) => write!( + Self::ErrorStringBounds(0, _, _) => write!(f, "{}", desc)?, + Self::ErrorStringBounds(1, index, _) => write!( f, - "String index {} is out of bounds: only one character in the string ({})", - index, pos - ), - Self::ErrorStringBounds(max, index, pos) => write!( + "String index {} is out of bounds: only one character in the string", + index + )?, + Self::ErrorStringBounds(max, index, _) => write!( f, - "String index {} is out of bounds: only {} characters in the string ({})", - index, max, pos - ), + "String index {} is out of bounds: only {} characters in the string", + index, max + )?, } - } -} -impl From for Box { - fn from(err: ParseError) -> Self { - Box::new(EvalAltResult::ErrorParsing(err)) + // Do not write any position if None + if !pos.is_none() { + write!(f, " ({})", pos)?; + } + + Ok(()) } } @@ -261,9 +255,8 @@ impl EvalAltResult { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, pos, _) => *pos, - Self::ErrorParsing(err) => err.position(), - - Self::ErrorFunctionNotFound(_, pos) + Self::ErrorParsing(_, pos) + | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) @@ -299,9 +292,8 @@ impl EvalAltResult { #[cfg(not(feature = "no_std"))] Self::ErrorReadingScriptFile(_, pos, _) => *pos = new_position, - Self::ErrorParsing(err) => err.1 = new_position, - - Self::ErrorFunctionNotFound(_, pos) + Self::ErrorParsing(_, pos) + | Self::ErrorFunctionNotFound(_, pos) | Self::ErrorInFunctionCall(_, _, pos) | Self::ErrorBooleanArgMismatch(_, pos) | Self::ErrorCharMismatch(pos) diff --git a/tests/call_fn.rs b/tests/call_fn.rs index e3c61765..0bed8d7b 100644 --- a/tests/call_fn.rs +++ b/tests/call_fn.rs @@ -1,19 +1,17 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, Func, ParseErrorType, Scope, INT}; +use rhai::{Engine, EvalAltResult, Func, ParseError, ParseErrorType, Scope, INT}; #[test] fn test_fn() -> Result<(), Box> { let engine = Engine::new(); // Expect duplicated parameters error - match engine - .compile("fn hello(x, x) { x }") - .expect_err("should be error") - .error_type() - { - ParseErrorType::FnDuplicatedParam(f, p) if f == "hello" && p == "x" => (), - _ => assert!(false, "wrong error"), - } + assert!(matches!( + engine + .compile("fn hello(x, x) { x }") + .expect_err("should be error"), + ParseError(x, _) if *x == ParseErrorType::FnDuplicatedParam("hello".to_string(), "x".to_string()) + )); Ok(()) } diff --git a/tests/constants.rs b/tests/constants.rs index d5e0820a..266b6ac2 100644 --- a/tests/constants.rs +++ b/tests/constants.rs @@ -7,14 +7,16 @@ fn test_constant() -> Result<(), Box> { assert_eq!(engine.eval::("const x = 123; x")?, 123); assert!(matches!( - *engine.eval::("const x = 123; x = 42;").expect_err("expects error"), - EvalAltResult::ErrorParsing(err) if err.error_type() == &ParseErrorType::AssignmentToConstant("x".to_string()) + *engine + .eval::("const x = 123; x = 42;") + .expect_err("expects error"), + EvalAltResult::ErrorParsing(ParseErrorType::AssignmentToConstant(x), _) if x == "x" )); #[cfg(not(feature = "no_index"))] assert!(matches!( *engine.eval::("const x = [1, 2, 3, 4, 5]; x[2] = 42;").expect_err("expects error"), - EvalAltResult::ErrorParsing(err) if err.error_type() == &ParseErrorType::AssignmentToConstant("x".to_string()) + EvalAltResult::ErrorParsing(ParseErrorType::AssignmentToConstant(x), _) if x == "x" )); Ok(()) diff --git a/tests/expressions.rs b/tests/expressions.rs index 75790f00..771d1d02 100644 --- a/tests/expressions.rs +++ b/tests/expressions.rs @@ -21,6 +21,8 @@ fn test_expressions() -> Result<(), Box> { assert!(engine.eval_expression::<()>("x = 42").is_err()); assert!(engine.compile_expression("let x = 42").is_err()); + engine.compile("40 + { let x = 2; x }")?; + Ok(()) } diff --git a/tests/looping.rs b/tests/looping.rs index a973c98f..3a4804ce 100644 --- a/tests/looping.rs +++ b/tests/looping.rs @@ -1,4 +1,4 @@ -use rhai::{Engine, EvalAltResult, INT}; +use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType, INT}; #[test] fn test_loop() -> Result<(), Box> { @@ -26,5 +26,15 @@ fn test_loop() -> Result<(), Box> { 21 ); + assert!(matches!( + engine.compile("let x = 0; break;").expect_err("should error"), + ParseError(x, _) if *x == ParseErrorType::LoopBreak + )); + + assert!(matches!( + engine.compile("let x = 0; if x > 0 { continue; }").expect_err("should error"), + ParseError(x, _) if *x == ParseErrorType::LoopBreak + )); + Ok(()) } diff --git a/tests/modules.rs b/tests/modules.rs index a063e1bd..10c138ad 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -1,5 +1,7 @@ #![cfg(not(feature = "no_module"))] -use rhai::{module_resolvers, Engine, EvalAltResult, Module, Scope, INT}; +use rhai::{ + module_resolvers, Engine, EvalAltResult, Module, ParseError, ParseErrorType, Scope, INT, +}; #[test] fn test_module() { @@ -231,3 +233,20 @@ fn test_module_from_ast() -> Result<(), Box> { Ok(()) } + +#[test] +fn test_module_export() -> Result<(), Box> { + let engine = Engine::new(); + + assert!(matches!( + engine.compile(r"let x = 10; { export x; }").expect_err("should error"), + ParseError(x, _) if *x == ParseErrorType::WrongExport + )); + + assert!(matches!( + engine.compile(r"fn abc(x) { export x; }").expect_err("should error"), + ParseError(x, _) if *x == ParseErrorType::WrongExport + )); + + Ok(()) +} diff --git a/tests/stack.rs b/tests/stack.rs index 1ba72e6a..29828fef 100644 --- a/tests/stack.rs +++ b/tests/stack.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "unchecked"))] -use rhai::{Engine, EvalAltResult, ParseErrorType}; +use rhai::{Engine, EvalAltResult, ParseError, ParseErrorType}; #[test] #[cfg(not(feature = "no_function"))] @@ -37,7 +37,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { engine.compile(r" let a = (1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+(1+1)))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))) ").expect_err("should error"), - err if err.error_type() == &ParseErrorType::ExprTooDeep + ParseError(x, _) if *x == ParseErrorType::ExprTooDeep )); engine.set_max_expr_depths(100, 6); @@ -71,7 +71,7 @@ fn test_stack_overflow_parsing() -> Result<(), Box> { 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 ").expect_err("should error"), - err if err.error_type() == &ParseErrorType::ExprTooDeep + ParseError(x, _) if *x == ParseErrorType::ExprTooDeep )); #[cfg(not(feature = "no_function"))]