diff --git a/src/parser.rs b/src/parser.rs index 08c5b6b..3e448e7 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,6 +1,7 @@ use miette::SourceSpan; use paste::paste; +use crate::span::Span; use crate::{ ast::{Ast, BinaryOp, Expr, Literal, Lval, Stmt, UnaryOp}, match_token::MatchToken, @@ -93,21 +94,34 @@ impl Parser { return self.parse_stmt(); }; - let (inner_stmts, close_brace) = self.parse_inner_block_until(MatchToken::RightBrace)?; - - Ok(Stmt::block(open_brace, inner_stmts, close_brace)) + match self.parse_block_body() { + Ok((inner_stmts, close_brace)) => Ok(Stmt::block(open_brace, inner_stmts, close_brace)), + Err(ParseBlockError::ReachedEnd { last_stmt_span }) => { + let end = last_stmt_span + .map(|s| s.end()) + .unwrap_or_else(|| open_brace.span.end()); + Err(ParserError::unclosed_block(&open_brace, end)) + } + Err(ParseBlockError::ParserError(e)) => Err(e), + } } - fn parse_inner_block_until( - &mut self, - until: MatchToken, - ) -> Result<(Vec>, Token)> { + /// Parse the body of a block into a group of statements + /// + /// Let the caller construct the `ParserError::UnclosedBlock`, as it has more context about + /// where the block began. + fn parse_block_body(&mut self) -> Result<(Vec>, Token), ParseBlockError> { let mut inner_stmts = Vec::new(); let close = loop { - if let Some(close) = self.pop_if_matches(until) { + if let Some(close) = self.pop_if_matches(MatchToken::RightBrace) { break close; } + if self.at_end() { + let last_stmt_span = inner_stmts.last().map(|s: &Spanned| s.span()); + return Err(ParseBlockError::ReachedEnd { last_stmt_span }); + } + let stmt = self.parse_decl()?; inner_stmts.push(stmt); }; @@ -390,6 +404,14 @@ pub enum ParserError { end: usize, }, + #[error("Unclosed block")] + UnclosedBlock { + #[label("block opens here")] + start: usize, + #[label("expected a closing `}}` after this")] + end: usize, + }, + #[error("Invalid target for assigment")] InvalidLval { kind: String, @@ -428,6 +450,11 @@ impl ParserError { Self::UnclosedGrouping { start, end } } + fn unclosed_block(left_brace: &Token, end: usize) -> Self { + let start = left_brace.span.start(); + Self::UnclosedBlock { start, end } + } + fn invalid_lval(invalid_target: &Spanned) -> Self { let kind = invalid_target.as_ref().as_str().to_string(); let span = invalid_target.span().into(); @@ -435,6 +462,14 @@ impl ParserError { } } +#[derive(Debug, thiserror::Error)] +enum ParseBlockError { + #[error(transparent)] + ParserError(#[from] ParserError), + #[error("Unclosed block")] + ReachedEnd { last_stmt_span: Option }, +} + #[cfg(test)] mod tests { use super::*;