From 9b59f6bf1973fe3f502f66c92650ffe517805e66 Mon Sep 17 00:00:00 2001 From: Jake Fecher Date: Mon, 24 Jun 2024 12:41:16 -0500 Subject: [PATCH] Add unclosed quote error --- compiler/noirc_frontend/src/lexer/errors.rs | 8 +++++- compiler/noirc_frontend/src/lexer/lexer.rs | 27 ++++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/compiler/noirc_frontend/src/lexer/errors.rs b/compiler/noirc_frontend/src/lexer/errors.rs index dbaf2f3a81e..2452e034c1c 100644 --- a/compiler/noirc_frontend/src/lexer/errors.rs +++ b/compiler/noirc_frontend/src/lexer/errors.rs @@ -29,6 +29,8 @@ pub enum LexerErrorKind { InvalidEscape { escaped: char, span: Span }, #[error("Invalid quote delimiter `{delimiter}`, valid delimiters are `{{`, `[`, and `(`")] InvalidQuoteDelimiter { delimiter: SpannedToken }, + #[error("Expected `{end_delim}` to close this {start_delim}")] + UnclosedQuote { start_delim: SpannedToken, end_delim: Token }, } impl From for ParserError { @@ -50,6 +52,7 @@ impl LexerErrorKind { LexerErrorKind::UnterminatedStringLiteral { span } => *span, LexerErrorKind::InvalidEscape { span, .. } => *span, LexerErrorKind::InvalidQuoteDelimiter { delimiter } => delimiter.to_span(), + LexerErrorKind::UnclosedQuote { start_delim, .. } => start_delim.to_span(), } } @@ -96,8 +99,11 @@ impl LexerErrorKind { LexerErrorKind::InvalidEscape { escaped, span } => (format!("'\\{escaped}' is not a valid escape sequence. Use '\\' for a literal backslash character."), "Invalid escape sequence".to_string(), *span), LexerErrorKind::InvalidQuoteDelimiter { delimiter } => { - ("Invalid quote delimiter `{delimiter}`".to_string(), "Valid delimiters are `{`, `[`, and `(`".to_string(), delimiter.to_span()) + (format!("Invalid quote delimiter `{delimiter}`"), "Valid delimiters are `{`, `[`, and `(`".to_string(), delimiter.to_span()) }, + LexerErrorKind::UnclosedQuote { start_delim, end_delim } => { + ("Unclosed `quote` expression".to_string(), format!("Expected a `{end_delim}` to close this `{start_delim}`"), start_delim.to_span()) + } } } } diff --git a/compiler/noirc_frontend/src/lexer/lexer.rs b/compiler/noirc_frontend/src/lexer/lexer.rs index 249103b3d15..d2bc3d0f519 100644 --- a/compiler/noirc_frontend/src/lexer/lexer.rs +++ b/compiler/noirc_frontend/src/lexer/lexer.rs @@ -536,15 +536,21 @@ impl<'a> Lexer<'a> { }; let mut tokens = Vec::new(); - let mut nested_delimiters = 1; - while nested_delimiters != 0 { + // Keep track of each nested delimiter we need to close. + let mut nested_delimiters = vec![delimiter]; + + while !nested_delimiters.is_empty() { let token = self.next_token()?; if *token.token() == start_delim { - nested_delimiters += 1; + nested_delimiters.push(token.clone()); } else if *token.token() == end_delim { - nested_delimiters -= 1; + nested_delimiters.pop(); + } else if *token.token() == Token::EOF { + let start_delim = + nested_delimiters.pop().expect("If this were empty, we wouldn't be looping"); + return Err(LexerErrorKind::UnclosedQuote { start_delim, end_delim }); } tokens.push(token); @@ -1312,4 +1318,17 @@ mod tests { } } } + + #[test] + fn test_unclosed_quote() { + let cases = vec!["quote {", "quote { { }", "quote [ []", "quote (((((((())))"]; + + for source in cases { + // `quote` is not itself a keyword so if the token stream fails to + // parse we don't expect any valid tokens from the quote construct + for token in Lexer::new(source) { + assert!(token.is_err(), "Expected Err, found {token:?}"); + } + } + } }