diff --git a/src/error.rs b/src/error.rs index 6c1d3857..9408b950 100644 --- a/src/error.rs +++ b/src/error.rs @@ -60,6 +60,8 @@ pub enum ParseErrorType { MissingRightBracket(String), /// A list of expressions is missing the separating ','. MissingComma(String), + /// A statement is missing the ending ';'. + MissingSemicolon(String), /// An expression in function call arguments `()` has syntax error. MalformedCallExpr(String), /// An expression in indexing brackets `[]` has syntax error. @@ -119,6 +121,7 @@ impl ParseError { #[cfg(not(feature = "no_index"))] ParseErrorType::MissingRightBracket(_) => "Expecting ']'", ParseErrorType::MissingComma(_) => "Expecting ','", + ParseErrorType::MissingSemicolon(_) => "Expecting ';'", ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments", #[cfg(not(feature = "no_index"))] ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression", @@ -130,7 +133,7 @@ impl ParseError { #[cfg(not(feature = "no_function"))] ParseErrorType::FnMissingParams(_) => "Expecting parameters in function declaration", #[cfg(not(feature = "no_function"))] - ParseErrorType::WrongFnDefinition => "Function definitions must be at top 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::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." @@ -168,7 +171,9 @@ 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::MissingSemicolon(ref 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/optimize.rs b/src/optimize.rs index a26a5d98..05c91304 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -451,7 +451,7 @@ pub(crate) fn optimize<'a>( if let Stmt::Const(name, value, _) = &stmt { // Load constants state.push_constant(name, value.as_ref().clone()); - stmt // Keep it in the top scope + stmt // Keep it in the global scope } else { // Keep all variable declarations at this level // and always keep the last return value @@ -470,7 +470,7 @@ pub(crate) fn optimize<'a>( // Eliminate code that is pure but always keep the last statement let last_stmt = result.pop(); - // Remove all pure statements at top level + // Remove all pure statements at global level result.retain(|stmt| !matches!(stmt, Stmt::Expr(expr) if expr.is_pure())); if let Some(stmt) = last_stmt { diff --git a/src/parser.rs b/src/parser.rs index 102e51b9..557e9ec9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -203,6 +203,23 @@ impl Stmt { matches!(self, Stmt::Let(_, _, _)) } + pub fn is_self_terminated(&self) -> bool { + match self { + Stmt::Noop(_) + | Stmt::IfElse(_, _, _) + | Stmt::While(_, _) + | Stmt::Loop(_) + | Stmt::For(_, _, _) + | Stmt::Block(_, _) => true, + + Stmt::Let(_, _, _) + | Stmt::Const(_, _, _) + | Stmt::Expr(_) + | Stmt::Break(_) + | Stmt::ReturnWithVal(_, _, _) => false, + } + } + pub fn position(&self) -> Position { match self { Stmt::Noop(pos) @@ -1913,22 +1930,42 @@ fn parse_block<'a>(input: &mut Peekable>) -> Result (), // empty block - #[cfg(not(feature = "no_function"))] - (Token::Fn, pos) => return Err(ParseError::new(PERR::WrongFnDefinition, *pos)), - _ => { while input.peek().is_some() { // Parse statements inside the block - statements.push(parse_stmt(input)?); + let stmt = parse_stmt(input)?; - // Notice semicolons are optional - if let Some((Token::SemiColon, _)) = input.peek() { - input.next(); - } + // See if it needs a terminating semicolon + let need_semicolon = !stmt.is_self_terminated(); + statements.push(stmt); + + // End block with right brace if let Some((Token::RightBrace, _)) = input.peek() { break; } + + match input.peek() { + Some((Token::SemiColon, _)) => { + input.next(); + } + Some((_, _)) if !need_semicolon => (), + + Some((_, pos)) => { + // Semicolons are not optional between statements + return Err(ParseError::new( + PERR::MissingSemicolon("terminating a statement".into()), + *pos, + )); + } + + None => { + return Err(ParseError::new( + PERR::MissingRightBrace("end of block".into()), + Position::eof(), + )) + } + } } } } @@ -1959,6 +1996,9 @@ fn parse_stmt<'a>(input: &mut Peekable>) -> Result return Err(ParseError::new(PERR::WrongFnDefinition, *pos)), + (Token::If, _) => parse_if(input), (Token::While, _) => parse_while(input), (Token::Loop, _) => parse_loop(input), @@ -2097,16 +2137,16 @@ fn parse_fn<'a>(input: &mut Peekable>) -> Result( +fn parse_global_level<'a, 'e>( input: &mut Peekable>, ) -> Result<(Vec, Vec), ParseError> { let mut statements = Vec::::new(); let mut functions = Vec::::new(); while input.peek().is_some() { - match input.peek().expect("should not be None") { - #[cfg(not(feature = "no_function"))] - (Token::Fn, _) => { + #[cfg(not(feature = "no_function"))] + { + if matches!(input.peek().expect("should not be None"), (Token::Fn, _)) { let f = parse_fn(input)?; // Ensure list is sorted @@ -2114,13 +2154,31 @@ fn parse_top_level<'a, 'e>( Ok(n) => functions[n] = f, // Override previous definition Err(n) => functions.insert(n, f), // New function definition } + + continue; } - _ => statements.push(parse_stmt(input)?), } - // Notice semicolons are optional - if let Some((Token::SemiColon, _)) = input.peek() { - input.next(); + let stmt = parse_stmt(input)?; + + let need_semicolon = !stmt.is_self_terminated(); + + statements.push(stmt); + + match input.peek() { + None => break, + Some((Token::SemiColon, _)) => { + input.next(); + } + Some((_, _)) if !need_semicolon => (), + + Some((_, pos)) => { + // Semicolons are not optional between statements + return Err(ParseError::new( + PERR::MissingSemicolon("terminating a statement".into()), + *pos, + )); + } } } @@ -2132,7 +2190,7 @@ pub fn parse<'a, 'e>( engine: &Engine<'e>, scope: &Scope, ) -> Result { - let (statements, functions) = parse_top_level(input)?; + let (statements, functions) = parse_global_level(input)?; Ok( #[cfg(not(feature = "no_optimize"))]