From 705fbd0c1b2ddcfa35ba0fd62bf39b04448da146 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 16 Mar 2020 23:51:32 +0800 Subject: [PATCH] Improve error messages to lists. --- Cargo.toml | 1 + README.md | 36 +-- src/engine.rs | 15 +- src/error.rs | 5 + src/parser.rs | 829 ++++++++++++++++++++++++-------------------------- 5 files changed, 433 insertions(+), 453 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 308685bf..0b776f30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ include = [ "scripts/*.rhai", "Cargo.toml" ] +keywords = [ "scripting" ] [dependencies] num-traits = "*" diff --git a/README.md b/README.md index 5a406fc6..09a5d6d4 100644 --- a/README.md +++ b/README.md @@ -171,7 +171,7 @@ let ast = engine.compile("40 + 2")?; for _ in 0..42 { let result: i64 = engine.eval_ast(&ast)?; - println!("Answer: {}", result); // prints 42 + println!("Answer #{}: {}", i, result); // prints 42 } ``` @@ -193,14 +193,17 @@ use rhai::Engine; let mut engine = Engine::new(); // Define a function in a script and load it into the Engine. -engine.consume(true, // pass true to 'retain_functions' otherwise these functions - r" // will be cleared at the end of consume() - fn hello(x, y) { // a function with two parameters: String and i64 - x.len() + y // returning i64 +// Pass true to 'retain_functions' otherwise these functions will be cleared at the end of consume() +engine.consume(true, + r" + // a function with two parameters: String and i64 + fn hello(x, y) { + x.len() + y } - fn hello(x) { // functions can be overloaded: this one takes only one parameter - x * 2 // returning i64 + // functions can be overloaded: this one takes only one parameter + fn hello(x) { + x * 2 } ")?; @@ -495,22 +498,21 @@ let result = engine.eval::("let x = new_ts(); x.foo()")?; println!("result: {}", result); // prints 1 ``` -`type_of` works fine with custom types and returns the name of the type: +`type_of` works fine with custom types and returns the name of the type. If `register_type_with_name` is used to register the custom type +with a special "pretty-print" name, `type_of` will return that name instead. ```rust let x = new_ts(); print(x.type_of()); // prints "foo::bar::TestStruct" + // prints "Hello" if TestStruct is registered with + // engine.register_type_with_name::("Hello")?; ``` -If `register_type_with_name` is used to register the custom type with a special "pretty-print" name, `type_of` will return that name instead. - Getters and setters ------------------- Similarly, custom types can expose members by registering a `get` and/or `set` function. -For example: - ```rust #[derive(Clone)] struct TestStruct { @@ -565,8 +567,8 @@ fn main() -> Result<(), EvalAltResult> // Then push some initialized variables into the state // NOTE: Remember the system number types in Rhai are i64 (i32 if 'only_i32') ond f64. // Better stick to them or it gets hard working with the script. - scope.push("y".into(), 42_i64); - scope.push("z".into(), 999_i64); + scope.push("y", 42_i64); + scope.push("z", 999_i64); // First invocation engine.eval_with_scope::<()>(&mut scope, r" @@ -684,7 +686,7 @@ Numeric operators generally follow C styles. | `%` | Modulo (remainder) | | | `~` | Power | | | `&` | Binary _And_ bit-mask | Yes | -| `|` | Binary _Or_ bit-mask | Yes | +| `\|` | Binary _Or_ bit-mask | Yes | | `^` | Binary _Xor_ bit-mask | Yes | | `<<` | Left bit-shift | Yes | | `>>` | Right bit-shift | Yes | @@ -948,9 +950,9 @@ Boolean operators | -------- | ------------------------------- | | `!` | Boolean _Not_ | | `&&` | Boolean _And_ (short-circuits) | -| `||` | Boolean _Or_ (short-circuits) | +| `\|\|` | Boolean _Or_ (short-circuits) | | `&` | Boolean _And_ (full evaluation) | -| `|` | Boolean _Or_ (full evaluation) | +| `\|` | Boolean _Or_ (full evaluation) | Double boolean operators `&&` and `||` _short-circuit_, meaning that the second operand will not be evaluated if the first one already proves the condition wrong. diff --git a/src/engine.rs b/src/engine.rs index bee73cff..4e75b9f5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1026,30 +1026,29 @@ impl Engine<'_> { // Block scope Stmt::Block(block, _) => { let prev_len = scope.len(); - let mut last_result: Result = Ok(().into_dynamic()); + let mut result: Result = Ok(().into_dynamic()); - for block_stmt in block.iter() { - last_result = self.eval_stmt(scope, block_stmt); + for stmt in block.iter() { + result = self.eval_stmt(scope, stmt); - if let Err(x) = last_result { - last_result = Err(x); + if result.is_err() { break; } } scope.rewind(prev_len); - last_result + result } // If-else statement - Stmt::IfElse(guard, body, else_body) => self + Stmt::IfElse(guard, if_body, else_body) => self .eval_expr(scope, guard)? .downcast::() .map_err(|_| EvalAltResult::ErrorIfGuard(guard.position())) .and_then(|guard_val| { if *guard_val { - self.eval_stmt(scope, body) + self.eval_stmt(scope, if_body) } else if let Some(stmt) = else_body { self.eval_stmt(scope, stmt.as_ref()) } else { diff --git a/src/error.rs b/src/error.rs index a582bcb2..6c1d3857 100644 --- a/src/error.rs +++ b/src/error.rs @@ -58,6 +58,8 @@ pub enum ParseErrorType { /// An open `[` is missing the corresponding closing `]`. #[cfg(not(feature = "no_index"))] MissingRightBracket(String), + /// A list of expressions is missing the separating ','. + MissingComma(String), /// An expression in function call arguments `()` has syntax error. MalformedCallExpr(String), /// An expression in indexing brackets `[]` has syntax error. @@ -116,6 +118,7 @@ impl ParseError { ParseErrorType::MissingRightBrace(_) => "Expecting '}'", #[cfg(not(feature = "no_index"))] ParseErrorType::MissingRightBracket(_) => "Expecting ']'", + ParseErrorType::MissingComma(_) => "Expecting ','", ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", #[cfg(not(feature = "no_index"))] ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", @@ -165,6 +168,8 @@ impl fmt::Display for ParseError { #[cfg(not(feature = "no_index"))] ParseErrorType::MissingRightBracket(ref s) => write!(f, "{} for {}", self.desc(), s)?, + ParseErrorType::MissingComma(ref s) => write!(f, "{} for {}", self.desc(), s)?, + ParseErrorType::AssignmentToConstant(ref s) if s.is_empty() => { write!(f, "{}", self.desc())? } diff --git a/src/parser.rs b/src/parser.rs index 366329c3..102e51b9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -42,9 +42,11 @@ pub struct Position { impl Position { /// Create a new `Position`. pub fn new(line: usize, position: usize) -> Self { - if line == 0 || (line == usize::MAX && position == usize::MAX) { - panic!("invalid position: ({}, {})", line, position); - } + assert!(line != 0, "line cannot be zero"); + assert!( + line != usize::MAX || position != usize::MAX, + "invalid position" + ); Self { line, @@ -295,9 +297,10 @@ impl Expr { | Expr::False(pos) | Expr::Unit(pos) => *pos, - Expr::Assignment(e, _, _) | Expr::Dot(e, _, _) | Expr::And(e, _) | Expr::Or(e, _) => { - e.position() - } + Expr::Assignment(expr, _, _) + | Expr::Dot(expr, _, _) + | Expr::And(expr, _) + | Expr::Or(expr, _) => expr.position(), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(_, pos) => *pos, @@ -306,7 +309,7 @@ impl Expr { Expr::Array(_, pos) => *pos, #[cfg(not(feature = "no_index"))] - Expr::Index(e, _, _) => e.position(), + Expr::Index(expr, _, _) => expr.position(), } } @@ -423,15 +426,15 @@ impl Token { pub fn syntax<'a>(&'a self) -> Cow<'a, str> { use self::Token::*; - match *self { - IntegerConstant(ref i) => i.to_string().into(), + match self { + IntegerConstant(i) => i.to_string().into(), #[cfg(not(feature = "no_float"))] - FloatConstant(ref f) => f.to_string().into(), - Identifier(ref s) => s.into(), - CharConstant(ref c) => c.to_string().into(), - LexError(ref err) => err.to_string().into(), + FloatConstant(f) => f.to_string().into(), + Identifier(s) => s.into(), + CharConstant(c) => c.to_string().into(), + LexError(err) => err.to_string().into(), - ref token => (match token { + token => (match token { StringConst(_) => "string", LeftBrace => "{", RightBrace => "}", @@ -505,7 +508,7 @@ impl Token { pub fn is_next_unary(&self) -> bool { use self::Token::*; - match *self { + match self { LexError(_) | LeftBrace | // (+expr) - is unary // RightBrace | {expr} - expr not unary & is closing @@ -562,28 +565,63 @@ impl Token { } } - #[allow(dead_code)] - pub fn is_binary_op(&self) -> bool { - use self::Token::*; + pub fn precedence(&self) -> u8 { + match self { + Self::Equals + | Self::PlusAssign + | Self::MinusAssign + | Self::MultiplyAssign + | Self::DivideAssign + | Self::LeftShiftAssign + | Self::RightShiftAssign + | Self::AndAssign + | Self::OrAssign + | Self::XOrAssign + | Self::ModuloAssign + | Self::PowerOfAssign => 10, - match *self { - RightParen | Plus | Minus | Multiply | Divide | Comma | Equals | LessThan - | GreaterThan | LessThanEqualsTo | GreaterThanEqualsTo | EqualsTo | NotEqualsTo - | Pipe | Or | Ampersand | And | PowerOf => true, + Self::Or | Self::XOr | Self::Pipe => 50, - #[cfg(not(feature = "no_index"))] - RightBrace | RightBracket => true, + Self::And | Self::Ampersand => 60, - _ => false, + Self::LessThan + | Self::LessThanEqualsTo + | Self::GreaterThan + | Self::GreaterThanEqualsTo + | Self::EqualsTo + | Self::NotEqualsTo => 70, + + Self::Plus | Self::Minus => 80, + + Self::Divide | Self::Multiply | Self::PowerOf => 90, + + Self::LeftShift | Self::RightShift => 100, + + Self::Modulo => 110, + + Self::Period => 120, + + _ => 0, } } - #[allow(dead_code)] - pub fn is_unary_op(&self) -> bool { - use self::Token::*; + pub fn is_bind_right(&self) -> bool { + match self { + Self::Equals + | Self::PlusAssign + | Self::MinusAssign + | Self::MultiplyAssign + | Self::DivideAssign + | Self::LeftShiftAssign + | Self::RightShiftAssign + | Self::AndAssign + | Self::OrAssign + | Self::XOrAssign + | Self::ModuloAssign + | Self::PowerOfAssign => true, + + Self::Period => true, - match *self { - UnaryPlus | UnaryMinus | Equals | Bang | Return | Throw => true, _ => false, } } @@ -637,89 +675,40 @@ impl<'a> TokenIterator<'a> { escape.clear(); result.push('\r'); } - 'x' if !escape.is_empty() => { + ch @ 'x' | ch @ 'u' | ch @ 'U' if !escape.is_empty() => { let mut seq = escape.clone(); - seq.push('x'); + seq.push(ch); escape.clear(); + let mut out_val: u32 = 0; - for _ in 0..2 { - if let Some(c) = self.char_stream.next() { - seq.push(c); - self.advance(); + let len = match ch { + 'x' => 2, + 'u' => 4, + 'U' => 8, + _ => panic!("should be 'x', 'u' or 'U'"), + }; - if let Some(d1) = c.to_digit(16) { - out_val *= 16; - out_val += d1; - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } + for _ in 0..len { + let c = self.char_stream.next().ok_or_else(|| { + (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) + })?; + + seq.push(c); + self.advance(); + + out_val *= 16; + out_val += c.to_digit(16).ok_or_else(|| { + (LERR::MalformedEscapeSequence(seq.to_string()), self.pos) + })?; } - if let Some(r) = char::from_u32(out_val) { - result.push(r); - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } + result.push( + char::from_u32(out_val) + .ok_or_else(|| (LERR::MalformedEscapeSequence(seq), self.pos))?, + ); } - 'u' if !escape.is_empty() => { - let mut seq = escape.clone(); - seq.push('u'); - escape.clear(); - let mut out_val: u32 = 0; - for _ in 0..4 { - if let Some(c) = self.char_stream.next() { - seq.push(c); - self.advance(); - - if let Some(d1) = c.to_digit(16) { - out_val *= 16; - out_val += d1; - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } - } - - if let Some(r) = char::from_u32(out_val) { - result.push(r); - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } - } - 'U' if !escape.is_empty() => { - let mut seq = escape.clone(); - seq.push('U'); - escape.clear(); - let mut out_val: u32 = 0; - for _ in 0..8 { - if let Some(c) = self.char_stream.next() { - seq.push(c); - self.advance(); - - if let Some(d1) = c.to_digit(16) { - out_val *= 16; - out_val += d1; - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } - } - - if let Some(r) = char::from_u32(out_val) { - result.push(r); - } else { - return Err((LERR::MalformedEscapeSequence(seq), self.pos)); - } - } - x if enclosing_char == x && !escape.is_empty() => result.push(x), - x if enclosing_char == x && escape.is_empty() => break, + ch if enclosing_char == ch && !escape.is_empty() => result.push(ch), + ch if enclosing_char == ch && escape.is_empty() => break, _ if !escape.is_empty() => { return Err((LERR::MalformedEscapeSequence(escape), self.pos)) } @@ -727,9 +716,9 @@ impl<'a> TokenIterator<'a> { self.rewind(); return Err((LERR::UnterminatedString, self.pos)); } - x => { + ch => { escape.clear(); - result.push(x); + result.push(ch); } } } @@ -775,54 +764,47 @@ impl<'a> TokenIterator<'a> { } } } - 'x' | 'X' if c == '0' => { + ch @ 'x' | ch @ 'X' | ch @ 'o' | ch @ 'O' | ch @ 'b' | ch @ 'B' + if c == '0' => + { result.push(next_char); self.char_stream.next(); self.advance(); + + let valid = match ch { + 'x' | 'X' => [ + 'a', 'b', 'c', 'd', 'e', 'f', 'A', 'B', 'C', 'D', 'E', 'F', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '_', + ], + 'o' | 'O' => [ + '0', '1', '2', '3', '4', '5', '6', '7', '_', '_', '_', '_', + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + ], + 'b' | 'B' => [ + '0', '1', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', '_', + ], + _ => panic!("unexpected character {}", ch), + }; + + radix_base = Some(match ch { + 'x' | 'X' => 16, + 'o' | 'O' => 8, + 'b' | 'B' => 2, + _ => panic!("unexpected character {}", ch), + }); + while let Some(&next_char_in_hex) = self.char_stream.peek() { - match next_char_in_hex { - '0'..='9' | 'a'..='f' | 'A'..='F' | '_' => { - result.push(next_char_in_hex); - self.char_stream.next(); - self.advance(); - } - _ => break, + if !valid.contains(&next_char_in_hex) { + break; } + + result.push(next_char_in_hex); + self.char_stream.next(); + self.advance(); } - radix_base = Some(16); - } - 'o' | 'O' if c == '0' => { - result.push(next_char); - self.char_stream.next(); - self.advance(); - while let Some(&next_char_in_oct) = self.char_stream.peek() { - match next_char_in_oct { - '0'..='8' | '_' => { - result.push(next_char_in_oct); - self.char_stream.next(); - self.advance(); - } - _ => break, - } - } - radix_base = Some(8); - } - 'b' | 'B' if c == '0' => { - result.push(next_char); - self.char_stream.next(); - self.advance(); - while let Some(&next_char_in_binary) = self.char_stream.peek() { - match next_char_in_binary { - '0' | '1' | '_' => { - result.push(next_char_in_binary); - self.char_stream.next(); - self.advance(); - } - _ => break, - } - } - radix_base = Some(2); } + _ => break, } } @@ -844,25 +826,15 @@ impl<'a> TokenIterator<'a> { )); } else { let out: String = result.iter().filter(|&&c| c != '_').collect(); - - #[cfg(feature = "no_float")] - return Some(( - INT::from_str(&out) - .map(Token::IntegerConstant) - .unwrap_or_else(|_| { - Token::LexError(LERR::MalformedNumber(result.iter().collect())) - }), - pos, - )); + let num = INT::from_str(&out).map(Token::IntegerConstant); #[cfg(not(feature = "no_float"))] + let num = num.or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)); + return Some(( - INT::from_str(&out) - .map(Token::IntegerConstant) - .or_else(|_| FLOAT::from_str(&out).map(Token::FloatConstant)) - .unwrap_or_else(|_| { - Token::LexError(LERR::MalformedNumber(result.iter().collect())) - }), + num.unwrap_or_else(|_| { + Token::LexError(LERR::MalformedNumber(result.iter().collect())) + }), pos, )); } @@ -1217,67 +1189,6 @@ pub fn lex(input: &str) -> TokenIterator<'_> { } } -fn get_precedence(token: &Token) -> u8 { - match *token { - Token::Equals - | Token::PlusAssign - | Token::MinusAssign - | Token::MultiplyAssign - | Token::DivideAssign - | Token::LeftShiftAssign - | Token::RightShiftAssign - | Token::AndAssign - | Token::OrAssign - | Token::XOrAssign - | Token::ModuloAssign - | Token::PowerOfAssign => 10, - - Token::Or | Token::XOr | Token::Pipe => 50, - - Token::And | Token::Ampersand => 60, - - Token::LessThan - | Token::LessThanEqualsTo - | Token::GreaterThan - | Token::GreaterThanEqualsTo - | Token::EqualsTo - | Token::NotEqualsTo => 70, - - Token::Plus | Token::Minus => 80, - - Token::Divide | Token::Multiply | Token::PowerOf => 90, - - Token::LeftShift | Token::RightShift => 100, - - Token::Modulo => 110, - - Token::Period => 120, - - _ => 0, - } -} - -fn is_bind_right(token: &Token) -> bool { - match *token { - Token::Equals - | Token::PlusAssign - | Token::MinusAssign - | Token::MultiplyAssign - | Token::DivideAssign - | Token::LeftShiftAssign - | Token::RightShiftAssign - | Token::AndAssign - | Token::OrAssign - | Token::XOrAssign - | Token::ModuloAssign - | Token::PowerOfAssign => true, - - Token::Period => true, - - _ => false, - } -} - fn parse_paren_expr<'a>( input: &mut Peekable>, begin: Position, @@ -1315,7 +1226,15 @@ fn parse_call_expr<'a>( ) -> Result { let mut args_expr_list = Vec::new(); - if let Some(&(Token::RightParen, _)) = input.peek() { + if let (Token::RightParen, _) = input.peek().ok_or_else(|| { + ParseError::new( + PERR::MissingRightParen(format!( + "closing the arguments to call of function '{}'", + id + )), + Position::eof(), + ) + })? { input.next(); return Ok(Expr::FunctionCall(id, args_expr_list, None, begin)); } @@ -1323,28 +1242,27 @@ fn parse_call_expr<'a>( loop { args_expr_list.push(parse_expr(input)?); - match input.peek() { - Some(&(Token::RightParen, _)) => { + match input.peek().ok_or_else(|| { + ParseError::new( + PERR::MissingRightParen(format!( + "closing the arguments to call of function '{}'", + id + )), + Position::eof(), + ) + })? { + (Token::RightParen, _) => { input.next(); return Ok(Expr::FunctionCall(id, args_expr_list, None, begin)); } - Some(&(Token::Comma, _)) => (), - Some(&(_, pos)) => { + (Token::Comma, _) => (), + (_, pos) => { return Err(ParseError::new( - PERR::MissingRightParen(format!( - "closing the parameters list to function call of '{}'", + PERR::MissingComma(format!( + "separating the arguments to call of function '{}'", id )), - pos, - )) - } - None => { - return Err(ParseError::new( - PERR::MissingRightParen(format!( - "closing the parameters list to function call of '{}'", - id - )), - Position::eof(), + *pos, )) } } @@ -1419,21 +1337,20 @@ fn parse_index_expr<'a>( } // Check if there is a closing bracket - match input.peek() { - Some(&(Token::RightBracket, _)) => { + match input.peek().ok_or_else(|| { + ParseError::new( + PERR::MissingRightBracket("index expression".into()), + Position::eof(), + ) + })? { + (Token::RightBracket, _) => { input.next(); return Ok(Expr::Index(lhs, Box::new(idx_expr), pos)); } - Some(&(_, pos)) => { + (_, pos) => { return Err(ParseError::new( PERR::MissingRightBracket("index expression".into()), - pos, - )) - } - None => { - return Err(ParseError::new( - PERR::MissingRightBracket("index expression".into()), - Position::eof(), + *pos, )) } } @@ -1445,12 +1362,13 @@ fn parse_ident_expr<'a>( begin: Position, ) -> Result { match input.peek() { - Some(&(Token::LeftParen, _)) => { + Some((Token::LeftParen, _)) => { input.next(); parse_call_expr(id, input, begin) } #[cfg(not(feature = "no_index"))] - Some(&(Token::LeftBracket, pos)) => { + Some((Token::LeftBracket, pos)) => { + let pos = *pos; input.next(); parse_index_expr(Box::new(Expr::Variable(id, begin)), input, pos) } @@ -1466,36 +1384,43 @@ fn parse_array_expr<'a>( ) -> Result { let mut arr = Vec::new(); - match input.peek() { - Some(&(Token::RightBracket, _)) => (), + if !matches!(input.peek(), Some((Token::RightBracket, _))) { + while input.peek().is_some() { + arr.push(parse_expr(input)?); - _ => { - while input.peek().is_some() { - arr.push(parse_expr(input)?); - - if let Some(&(Token::Comma, _)) = input.peek() { + match input.peek().ok_or_else(|| { + ParseError( + PERR::MissingRightBracket("separating items in array literal".into()), + Position::eof(), + ) + })? { + (Token::Comma, _) => { input.next(); } - - if let Some(&(Token::RightBracket, _)) = input.peek() { - break; + (Token::RightBracket, _) => break, + (_, pos) => { + return Err(ParseError( + PERR::MissingComma("separating items in array literal".into()), + *pos, + )) } } } } - match input.peek() { - Some(&(Token::RightBracket, _)) => { + match input.peek().ok_or_else(|| { + ParseError::new( + PERR::MissingRightBracket("the end of array literal".into()), + Position::eof(), + ) + })? { + (Token::RightBracket, _) => { input.next(); Ok(Expr::Array(arr, begin)) } - Some(&(_, pos)) => Err(ParseError::new( + (_, pos) => Err(ParseError::new( PERR::MissingRightBracket("the end of array literal".into()), - pos, - )), - None => Err(ParseError::new( - PERR::MissingRightBracket("the end of array literal".into()), - Position::eof(), + *pos, )), } } @@ -1503,8 +1428,9 @@ fn parse_array_expr<'a>( fn parse_primary<'a>(input: &mut Peekable>) -> Result { // Block statement as expression match input.peek() { - Some(&(Token::LeftBrace, pos)) => { - return parse_block(input).map(|block| Expr::Stmt(Box::new(block), pos)) + Some((Token::LeftBrace, pos)) => { + let pos = *pos; + return parse_block(input).map(|block| Expr::Stmt(Box::new(block), pos)); } _ => (), } @@ -1514,45 +1440,44 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result Ok(Expr::FloatConstant(x, pos)), + let mut root_expr = + match token.ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? { + #[cfg(not(feature = "no_float"))] + (Token::FloatConstant(x), pos) => Ok(Expr::FloatConstant(x, pos)), - Some((Token::IntegerConstant(x), pos)) => Ok(Expr::IntegerConstant(x, pos)), - Some((Token::CharConstant(c), pos)) => Ok(Expr::CharConstant(c, pos)), - Some((Token::StringConst(s), pos)) => { - can_be_indexed = true; - Ok(Expr::StringConstant(s, pos)) - } - Some((Token::Identifier(s), pos)) => { - can_be_indexed = true; - parse_ident_expr(s, input, pos) - } - Some((Token::LeftParen, pos)) => { - can_be_indexed = true; - parse_paren_expr(input, pos) - } - #[cfg(not(feature = "no_index"))] - Some((Token::LeftBracket, pos)) => { - can_be_indexed = true; - parse_array_expr(input, pos) - } - Some((Token::True, pos)) => Ok(Expr::True(pos)), - Some((Token::False, pos)) => Ok(Expr::False(pos)), - Some((Token::LexError(le), pos)) => { - Err(ParseError::new(PERR::BadInput(le.to_string()), pos)) - } - Some((token, pos)) => Err(ParseError::new( - PERR::BadInput(format!("Unexpected '{}'", token.syntax())), - pos, - )), - None => Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), - }?; + (Token::IntegerConstant(x), pos) => Ok(Expr::IntegerConstant(x, pos)), + (Token::CharConstant(c), pos) => Ok(Expr::CharConstant(c, pos)), + (Token::StringConst(s), pos) => { + can_be_indexed = true; + Ok(Expr::StringConstant(s, pos)) + } + (Token::Identifier(s), pos) => { + can_be_indexed = true; + parse_ident_expr(s, input, pos) + } + (Token::LeftParen, pos) => { + can_be_indexed = true; + parse_paren_expr(input, pos) + } + #[cfg(not(feature = "no_index"))] + (Token::LeftBracket, pos) => { + can_be_indexed = true; + parse_array_expr(input, pos) + } + (Token::True, pos) => Ok(Expr::True(pos)), + (Token::False, pos) => Ok(Expr::False(pos)), + (Token::LexError(le), pos) => Err(ParseError::new(PERR::BadInput(le.to_string()), pos)), + (token, pos) => Err(ParseError::new( + PERR::BadInput(format!("Unexpected '{}'", token.syntax())), + pos, + )), + }?; if can_be_indexed { // Tail processing all possible indexing #[cfg(not(feature = "no_index"))] - while let Some(&(Token::LeftBracket, pos)) = input.peek() { + while let Some((Token::LeftBracket, pos)) = input.peek() { + let pos = *pos; input.next(); root_expr = parse_index_expr(Box::new(root_expr), input, pos)?; } @@ -1562,13 +1487,18 @@ fn parse_primary<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { - match input.peek() { - Some(&(Token::UnaryMinus, pos)) => { + match input + .peek() + .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? + { + (Token::UnaryMinus, pos) => { + let pos = *pos; + input.next(); - match parse_unary(input) { + match parse_unary(input)? { // Negative integer - Ok(Expr::IntegerConstant(i, _)) => i + Expr::IntegerConstant(i, _) => i .checked_neg() .map(|x| Expr::IntegerConstant(x, pos)) .or_else(|| { @@ -1587,19 +1517,19 @@ fn parse_unary<'a>(input: &mut Peekable>) -> Result Ok(Expr::FloatConstant(-f, pos)), + Expr::FloatConstant(f, pos) => Ok(Expr::FloatConstant(-f, pos)), // Call negative function - Ok(expr) => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)), - - err @ Err(_) => err, + expr => Ok(Expr::FunctionCall("-".into(), vec![expr], None, pos)), } } - Some(&(Token::UnaryPlus, _)) => { + (Token::UnaryPlus, _) => { input.next(); parse_unary(input) } - Some(&(Token::Bang, pos)) => { + (Token::Bang, pos) => { + let pos = *pos; + input.next(); Ok(Expr::FunctionCall( @@ -1678,8 +1608,6 @@ fn parse_assignment(lhs: Expr, rhs: Expr, pos: Position) -> Result Ok(Expr::Assignment(Box::new(lhs), Box::new(rhs), pos)), Some(err) => Err(err), @@ -1709,8 +1637,8 @@ fn parse_binary_op<'a>( let mut current_lhs = lhs; loop { - let (current_precedence, bind_right) = if let Some(&(ref current_op, _)) = input.peek() { - (get_precedence(current_op), is_bind_right(current_op)) + let (current_precedence, bind_right) = if let Some((ref current_op, _)) = input.peek() { + (current_op.precedence(), current_op.is_bind_right()) } else { (0, false) }; @@ -1728,8 +1656,8 @@ fn parse_binary_op<'a>( let rhs = parse_unary(input)?; - let next_precedence = if let Some(&(ref next_op, _)) = input.peek() { - get_precedence(next_op) + let next_precedence = if let Some((next_op, _)) = input.peek() { + next_op.precedence() } else { 0 }; @@ -1863,26 +1791,21 @@ fn parse_if<'a>(input: &mut Peekable>) -> Result { - input.next(); + let else_body = if matches!(input.peek(), Some((Token::Else, _))) { + input.next(); - let else_body = if matches!(input.peek(), Some(&(Token::If, _))) { - parse_if(input)? - } else { - parse_block(input)? - }; + Some(Box::new(if matches!(input.peek(), Some((Token::If, _))) { + parse_if(input)? + } else { + parse_block(input)? + })) + } else { + None + }; - Ok(Stmt::IfElse( - Box::new(guard), - Box::new(body), - Some(Box::new(else_body)), - )) - } - _ => Ok(Stmt::IfElse(Box::new(guard), Box::new(body), None)), - } + Ok(Stmt::IfElse(Box::new(guard), Box::new(if_body), else_body)) } fn parse_while<'a>(input: &mut Peekable>) -> Result { @@ -1905,23 +1828,26 @@ fn parse_loop<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { input.next(); - let name = match input.next() { - Some((Token::Identifier(s), _)) => s, - Some((Token::LexError(s), pos)) => { + let name = match input + .next() + .ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))? + { + (Token::Identifier(s), _) => s, + (Token::LexError(s), pos) => { return Err(ParseError::new(PERR::BadInput(s.to_string()), pos)) } - Some((_, pos)) => return Err(ParseError::new(PERR::VariableExpected, pos)), - None => return Err(ParseError::new(PERR::VariableExpected, Position::eof())), + (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), }; - match input.next() { - Some((Token::In, _)) => (), - Some((_, pos)) => return Err(ParseError::new(PERR::MissingIn, pos)), - None => return Err(ParseError::new(PERR::MissingIn, Position::eof())), + match input + .next() + .ok_or_else(|| ParseError::new(PERR::MissingIn, Position::eof()))? + { + (Token::In, _) => (), + (_, pos) => return Err(ParseError::new(PERR::MissingIn, pos)), } let expr = parse_expr(input)?; - let body = parse_block(input)?; Ok(Stmt::For(name, Box::new(expr), Box::new(body))) @@ -1931,21 +1857,23 @@ fn parse_var<'a>( input: &mut Peekable>, var_type: VariableType, ) -> Result { - let pos = match input.next() { - Some((_, tok_pos)) => tok_pos, - _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), - }; + let pos = input + .next() + .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? + .1; - let name = match input.next() { - Some((Token::Identifier(s), _)) => s, - Some((Token::LexError(s), pos)) => { + let name = match input + .next() + .ok_or_else(|| ParseError::new(PERR::VariableExpected, Position::eof()))? + { + (Token::Identifier(s), _) => s, + (Token::LexError(s), pos) => { return Err(ParseError::new(PERR::BadInput(s.to_string()), pos)) } - Some((_, pos)) => return Err(ParseError::new(PERR::VariableExpected, pos)), - None => return Err(ParseError::new(PERR::VariableExpected, Position::eof())), + (_, pos) => return Err(ParseError::new(PERR::VariableExpected, pos)), }; - if matches!(input.peek(), Some(&(Token::Equals, _))) { + if matches!(input.peek(), Some((Token::Equals, _))) { input.next(); let init_value = parse_expr(input)?; @@ -1967,19 +1895,26 @@ fn parse_var<'a>( } fn parse_block<'a>(input: &mut Peekable>) -> Result { - let pos = match input.next() { - Some((Token::LeftBrace, pos)) => pos, - Some((_, pos)) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)), - None => return Err(ParseError::new(PERR::MissingLeftBrace, Position::eof())), + let pos = match input + .next() + .ok_or_else(|| ParseError::new(PERR::MissingLeftBrace, Position::eof()))? + { + (Token::LeftBrace, pos) => pos, + (_, pos) => return Err(ParseError::new(PERR::MissingLeftBrace, pos)), }; let mut statements = Vec::new(); - match input.peek() { - Some(&(Token::RightBrace, _)) => (), // empty block + match input.peek().ok_or_else(|| { + ParseError::new( + PERR::MissingRightBrace("end of block".into()), + Position::eof(), + ) + })? { + (Token::RightBrace, _) => (), // empty block #[cfg(not(feature = "no_function"))] - Some(&(Token::Fn, pos)) => return Err(ParseError::new(PERR::WrongFnDefinition, pos)), + (Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)), _ => { while input.peek().is_some() { @@ -1987,29 +1922,30 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result { + match input.peek().ok_or_else(|| { + ParseError::new( + PERR::MissingRightBrace("end of block".into()), + Position::eof(), + ) + })? { + (Token::RightBrace, _) => { input.next(); Ok(Stmt::Block(statements, pos)) } - Some(&(_, pos)) => Err(ParseError::new( + (_, pos) => Err(ParseError::new( PERR::MissingRightBrace("end of block".into()), - pos, - )), - None => Err(ParseError::new( - PERR::MissingRightBrace("end of block".into()), - Position::eof(), + *pos, )), } } @@ -2019,16 +1955,20 @@ fn parse_expr_stmt<'a>(input: &mut Peekable>) -> Result(input: &mut Peekable>) -> Result { - match input.peek() { - Some(&(Token::If, _)) => parse_if(input), - Some(&(Token::While, _)) => parse_while(input), - Some(&(Token::Loop, _)) => parse_loop(input), - Some(&(Token::For, _)) => parse_for(input), - Some(&(Token::Break, pos)) => { + match input + .peek() + .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? + { + (Token::If, _) => parse_if(input), + (Token::While, _) => parse_while(input), + (Token::Loop, _) => parse_loop(input), + (Token::For, _) => parse_for(input), + (Token::Break, pos) => { + let pos = *pos; input.next(); Ok(Stmt::Break(pos)) } - Some(&(ref token @ Token::Return, _)) | Some(&(ref token @ Token::Throw, _)) => { + (token @ Token::Return, _) | (token @ Token::Throw, _) => { let return_type = match token { Token::Return => ReturnType::Return, Token::Throw => ReturnType::Exception, @@ -2038,76 +1978,109 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result Ok(Stmt::ReturnWithVal(None, return_type, pos)), - // Just a return/throw without anything at the end of script + // return/throw at EOF None => Ok(Stmt::ReturnWithVal(None, return_type, Position::eof())), + // return; or throw; + Some((Token::SemiColon, pos)) => { + let pos = *pos; + Ok(Stmt::ReturnWithVal(None, return_type, pos)) + } // return or throw with expression - Some(&(_, pos)) => { + Some((_, pos)) => { + let pos = *pos; let ret = parse_expr(input)?; Ok(Stmt::ReturnWithVal(Some(Box::new(ret)), return_type, pos)) } } } - Some(&(Token::LeftBrace, _)) => parse_block(input), - Some(&(Token::Let, _)) => parse_var(input, VariableType::Normal), - Some(&(Token::Const, _)) => parse_var(input, VariableType::Constant), + (Token::LeftBrace, _) => parse_block(input), + (Token::Let, _) => parse_var(input, VariableType::Normal), + (Token::Const, _) => parse_var(input, VariableType::Constant), _ => parse_expr_stmt(input), } } #[cfg(not(feature = "no_function"))] fn parse_fn<'a>(input: &mut Peekable>) -> Result { - let pos = match input.next() { - Some((_, tok_pos)) => tok_pos, - _ => return Err(ParseError::new(PERR::InputPastEndOfFile, Position::eof())), + let pos = input + .next() + .ok_or_else(|| ParseError::new(PERR::InputPastEndOfFile, Position::eof()))? + .1; + + let name = match input + .next() + .ok_or_else(|| ParseError::new(PERR::FnMissingName, Position::eof()))? + { + (Token::Identifier(s), _) => s, + (_, pos) => return Err(ParseError::new(PERR::FnMissingName, pos)), }; - let name = match input.next() { - Some((Token::Identifier(s), _)) => s, - Some((_, pos)) => return Err(ParseError::new(PERR::FnMissingName, pos)), - None => return Err(ParseError::new(PERR::FnMissingName, Position::eof())), - }; - - match input.peek() { - Some(&(Token::LeftParen, _)) => { + match input + .peek() + .ok_or_else(|| ParseError::new(PERR::FnMissingParams(name.clone()), Position::eof()))? + { + (Token::LeftParen, _) => { input.next(); } - Some(&(_, pos)) => return Err(ParseError::new(PERR::FnMissingParams(name), pos)), - None => { - return Err(ParseError::new( - PERR::FnMissingParams(name), - Position::eof(), - )) - } + (_, pos) => return Err(ParseError::new(PERR::FnMissingParams(name), *pos)), } let mut params = Vec::new(); - if matches!(input.peek(), Some(&(Token::RightParen, _))) { + if matches!(input.peek(), Some((Token::RightParen, _))) { input.next(); } else { loop { - match input.next() { - Some((Token::RightParen, _)) => break, - Some((Token::Comma, _)) => (), - Some((Token::Identifier(s), _)) => { + match input.next().ok_or_else(|| { + ParseError::new( + PERR::MissingRightParen(format!( + "closing the parameters list of function '{}'", + name + )), + Position::eof(), + ) + })? { + (Token::Identifier(s), _) => { params.push(s.into()); } - Some((_, pos)) => { + (_, pos) => { return Err(ParseError::new( - PERR::MalformedCallExpr( - "Function call arguments missing either a ',' or a ')'".into(), - ), + PERR::MissingRightParen(format!( + "closing the parameters list of function '{}'", + name + )), pos, )) } - None => { + } + + match input.next().ok_or_else(|| { + ParseError::new( + PERR::MissingRightParen(format!( + "closing the parameters list of function '{}'", + name + )), + Position::eof(), + ) + })? { + (Token::RightParen, _) => break, + (Token::Comma, _) => (), + (Token::Identifier(_), _) => { return Err(ParseError::new( - PERR::MalformedCallExpr( - "Function call arguments missing a closing ')'".into(), - ), - Position::eof(), + PERR::MissingComma(format!( + "separating the parameters of function '{}'", + name + )), + pos, + )) + } + (_, pos) => { + return Err(ParseError::new( + PERR::MissingRightParen(format!( + "closing the parameters list of function '{}'", + name + )), + pos, )) } } @@ -2131,9 +2104,9 @@ fn parse_top_level<'a, 'e>( let mut functions = Vec::::new(); while input.peek().is_some() { - match input.peek() { + match input.peek().expect("should not be None") { #[cfg(not(feature = "no_function"))] - Some(&(Token::Fn, _)) => { + (Token::Fn, _) => { let f = parse_fn(input)?; // Ensure list is sorted @@ -2146,7 +2119,7 @@ fn parse_top_level<'a, 'e>( } // Notice semicolons are optional - if let Some(&(Token::SemiColon, _)) = input.peek() { + if let Some((Token::SemiColon, _)) = input.peek() { input.next(); } }