diff --git a/src/syntax/lexer.rs b/src/syntax/lexer.rs index ee73a595..d0e5c9bd 100644 --- a/src/syntax/lexer.rs +++ b/src/syntax/lexer.rs @@ -80,12 +80,6 @@ impl Lexer<'_> { self.error = Some((message.into(), ErrorPos::Full)); SyntaxKind::Error } - - /// Construct a positioned syntax error. - fn error_at_end(&mut self, message: impl Into) -> SyntaxKind { - self.error = Some((message.into(), ErrorPos::End)); - SyntaxKind::Error - } } /// Shared. @@ -209,7 +203,7 @@ impl Lexer<'_> { if self.s.eat_if("u{") { let hex = self.s.eat_while(char::is_ascii_alphanumeric); if !self.s.eat_if('}') { - return self.error_at_end("expected closing brace"); + return self.error("unclosed unicode escape sequence"); } if u32::from_str_radix(hex, 16) @@ -251,20 +245,15 @@ impl Lexer<'_> { } if found != backticks { - let remaining = backticks - found; - let noun = if remaining == 1 { "backtick" } else { "backticks" }; - return self.error_at_end(if found == 0 { - eco_format!("expected {} {}", remaining, noun) - } else { - eco_format!("expected {} more {}", remaining, noun) - }); + return self.error("unclosed raw text"); } SyntaxKind::Raw } fn link(&mut self) -> SyntaxKind { - let mut bracket_stack = Vec::new(); + let mut brackets = Vec::new(); + #[rustfmt::skip] self.s.eat_while(|c: char| { match c { @@ -275,20 +264,24 @@ impl Lexer<'_> { | ',' | '-' | '.' | '/' | ':' | ';' | '=' | '?' | '@' | '_' | '~' | '\'' => true, '[' => { - bracket_stack.push(SyntaxKind::LeftBracket); + brackets.push(SyntaxKind::LeftBracket); true } '(' => { - bracket_stack.push(SyntaxKind::LeftParen); + brackets.push(SyntaxKind::LeftParen); true } - ']' => bracket_stack.pop() == Some(SyntaxKind::LeftBracket), - ')' => bracket_stack.pop() == Some(SyntaxKind::LeftParen), + ']' => brackets.pop() == Some(SyntaxKind::LeftBracket), + ')' => brackets.pop() == Some(SyntaxKind::LeftParen), _ => false, } }); - if !bracket_stack.is_empty() { - return self.error_at_end("expected closing bracket in link"); + + if !brackets.is_empty() { + return self.error( + "automatic links cannot contain unbalanced brackets, \ + use the `link` function instead", + ); } // Don't include the trailing characters likely to be part of text. @@ -328,7 +321,7 @@ impl Lexer<'_> { } if !self.s.eat_if('>') { - return self.error_at_end("expected closing angle bracket"); + return self.error("unclosed label"); } SyntaxKind::Label @@ -620,7 +613,7 @@ impl Lexer<'_> { }); if !self.s.eat_if('"') { - return self.error_at_end("expected quote"); + return self.error("unclosed string"); } SyntaxKind::Str diff --git a/src/syntax/parser.rs b/src/syntax/parser.rs index c7dbc936..3b82ce16 100644 --- a/src/syntax/parser.rs +++ b/src/syntax/parser.rs @@ -71,7 +71,7 @@ pub(super) fn reparse_markup( match p.current() { SyntaxKind::LeftBracket => *nesting += 1, SyntaxKind::RightBracket if *nesting > 0 => *nesting -= 1, - _ if stop(p.current) => break, + _ if stop(p.current()) => break, _ => {} } @@ -141,7 +141,7 @@ fn strong(p: &mut Parser) { || p.at(SyntaxKind::Parbreak) || p.at(SyntaxKind::RightBracket) }); - p.expect(SyntaxKind::Star); + p.expect_closing_delimiter(m, SyntaxKind::Star); p.wrap(m, SyntaxKind::Strong); } @@ -153,7 +153,7 @@ fn emph(p: &mut Parser) { || p.at(SyntaxKind::Parbreak) || p.at(SyntaxKind::RightBracket) }); - p.expect(SyntaxKind::Underscore); + p.expect_closing_delimiter(m, SyntaxKind::Underscore); p.wrap(m, SyntaxKind::Emph); } @@ -220,15 +220,15 @@ fn equation(p: &mut Parser) { let m = p.marker(); p.enter(LexMode::Math); p.assert(SyntaxKind::Dollar); - math(p, |kind| kind == SyntaxKind::Dollar); - p.expect(SyntaxKind::Dollar); + math(p, |p| p.at(SyntaxKind::Dollar)); + p.expect_closing_delimiter(m, SyntaxKind::Dollar); p.exit(); p.wrap(m, SyntaxKind::Equation); } -fn math(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { +fn math(p: &mut Parser, mut stop: impl FnMut(&Parser) -> bool) { let m = p.marker(); - while !p.eof() && !stop(p.current()) { + while !p.eof() && !stop(p) { let prev = p.prev_end(); math_expr(p); if !p.progress(prev) { @@ -514,22 +514,18 @@ fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option) { } } -fn code(p: &mut Parser, stop: impl FnMut(SyntaxKind) -> bool) { +fn code(p: &mut Parser, stop: impl FnMut(&Parser) -> bool) { let m = p.marker(); code_exprs(p, stop); p.wrap(m, SyntaxKind::Code); } -fn code_exprs(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { - while !p.eof() && !stop(p.current()) { +fn code_exprs(p: &mut Parser, mut stop: impl FnMut(&Parser) -> bool) { + while !p.eof() && !stop(p) { p.stop_at_newline(true); let prev = p.prev_end(); code_expr(p); - if p.progress(prev) - && !p.eof() - && !stop(p.current()) - && !p.eat_if(SyntaxKind::Semicolon) - { + if p.progress(prev) && !p.eof() && !stop(p) && !p.eat_if(SyntaxKind::Semicolon) { p.expected("semicolon or line break"); } p.unstop(); @@ -725,8 +721,12 @@ fn code_block(p: &mut Parser) { p.enter(LexMode::Code); p.stop_at_newline(false); p.assert(SyntaxKind::LeftBrace); - code(p, |kind| kind == SyntaxKind::RightBrace); - p.expect(SyntaxKind::RightBrace); + code(p, |p| { + p.at(SyntaxKind::RightBrace) + || p.at(SyntaxKind::RightBracket) + || p.at(SyntaxKind::RightParen) + }); + p.expect_closing_delimiter(m, SyntaxKind::RightBrace); p.exit(); p.unstop(); p.wrap(m, SyntaxKind::CodeBlock); @@ -737,7 +737,7 @@ fn content_block(p: &mut Parser) { p.enter(LexMode::Markup); p.assert(SyntaxKind::LeftBracket); markup(p, true, 0, |p| p.at(SyntaxKind::RightBracket)); - p.expect(SyntaxKind::RightBracket); + p.expect_closing_delimiter(m, SyntaxKind::RightBracket); p.exit(); p.wrap(m, SyntaxKind::ContentBlock); } @@ -800,6 +800,8 @@ fn invalidate_destructuring(p: &mut Parser, m: Marker) { fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { p.stop_at_newline(false); + + let m = p.marker(); p.assert(SyntaxKind::LeftParen); let mut count = 0; @@ -845,7 +847,7 @@ fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind { } } - p.expect(SyntaxKind::RightParen); + p.expect_closing_delimiter(m, SyntaxKind::RightParen); p.unstop(); if parenthesized && count == 1 { @@ -1586,6 +1588,12 @@ impl<'s> Parser<'s> { at } + fn expect_closing_delimiter(&mut self, open: Marker, kind: SyntaxKind) { + if !self.eat_if(kind) { + self.nodes[open.0].convert_to_error("unclosed delimiter"); + } + } + fn expected(&mut self, thing: &str) { self.unskip(); if self diff --git a/src/syntax/reparser.rs b/src/syntax/reparser.rs index c744eeb2..9e2b0a1b 100644 --- a/src/syntax/reparser.rs +++ b/src/syntax/reparser.rs @@ -306,15 +306,15 @@ mod tests { test("Hello #{ x + 1 }!", 9..10, "abc", true); test("A#{}!", 3..3, "\"", false); test("#{ [= x] }!", 5..5, "=", true); - test("#[[]]", 3..3, "\\", false); - test("#[[ab]]", 4..5, "\\", false); + test("#[[]]", 3..3, "\\", true); + test("#[[ab]]", 4..5, "\\", true); test("#{}}", 2..2, "{", false); test("A: #[BC]", 6..6, "{", true); - test("A: #[BC]", 6..6, "#{", false); + test("A: #[BC]", 6..6, "#{", true); test("A: #[BC]", 6..6, "#{}", true); test("#{\"ab\"}A", 5..5, "c", true); test("#{\"ab\"}A", 5..6, "c", false); - test("a#[]b", 3..3, "#{", false); + test("a#[]b", 3..3, "#{", true); test("a#{call(); abc}b", 8..8, "[]", true); test("a #while x {\n g(x) \n} b", 12..12, "//", true); test("a#[]b", 3..3, "[hey]", true); diff --git a/tests/typ/compiler/array.typ b/tests/typ/compiler/array.typ index 59e9f70b..a96a800f 100644 --- a/tests/typ/compiler/array.typ +++ b/tests/typ/compiler/array.typ @@ -256,10 +256,10 @@ #(1, 2, 3).at(-4) --- -// Error: 4 expected closing paren +// Error: 3-4 unclosed delimiter #{(} -// Error: 3-4 unexpected closing paren +// Error: 2-3 unclosed delimiter #{)} // Error: 4-6 unexpected end of block comment diff --git a/tests/typ/compiler/block.typ b/tests/typ/compiler/block.typ index 81c719da..386994e9 100644 --- a/tests/typ/compiler/block.typ +++ b/tests/typ/compiler/block.typ @@ -138,7 +138,7 @@ } --- -// Error: 3 expected closing brace +// Error: 2-3 unclosed delimiter #{ --- diff --git a/tests/typ/compiler/call.typ b/tests/typ/compiler/call.typ index 04eb2906..77e17b50 100644 --- a/tests/typ/compiler/call.typ +++ b/tests/typ/compiler/call.typ @@ -94,13 +94,14 @@ #func((x):1) --- -// Error: 2:1 expected closing bracket +// Error: 6-7 unclosed delimiter #func[`a]` --- -// Error: 8 expected closing paren +// Error: 7-8 unclosed delimiter #{func(} --- -// Error: 2:1 expected quote +// Error: 6-7 unclosed delimiter +// Error: 1:7-2:1 unclosed string #func("] diff --git a/tests/typ/compiler/let.typ b/tests/typ/compiler/let.typ index d9940a8c..825f9e7b 100644 --- a/tests/typ/compiler/let.typ +++ b/tests/typ/compiler/let.typ @@ -227,7 +227,7 @@ Three // Terminated by semicolon even though we are in a paren group. // Error: 18 expected expression -// Error: 18 expected closing paren +// Error: 11-12 unclosed delimiter #let v5 = (1, 2 + ; Five // Error: 9-13 expected identifier, found boolean diff --git a/tests/typ/compiler/ops-prec.typ b/tests/typ/compiler/ops-prec.typ index b90030f8..d3fe01b5 100644 --- a/tests/typ/compiler/ops-prec.typ +++ b/tests/typ/compiler/ops-prec.typ @@ -32,5 +32,5 @@ #test((1), 1) #test((1+2)*-3, -9) -// Error: 14 expected closing paren +// Error: 8-9 unclosed delimiter #test({(1 + 1}, 2) diff --git a/tests/typ/compiler/string.typ b/tests/typ/compiler/string.typ index ddd0f7ff..9a4b4146 100644 --- a/tests/typ/compiler/string.typ +++ b/tests/typ/compiler/string.typ @@ -204,5 +204,5 @@ #test("a123c".split(regex("\d+")), ("a", "c")) --- -// Error: 2:1 expected quote +// Error: 2-2:1 unclosed string #"hello\" diff --git a/tests/typ/math/syntax.typ b/tests/typ/math/syntax.typ index 8970fc93..503d3031 100644 --- a/tests/typ/math/syntax.typ +++ b/tests/typ/math/syntax.typ @@ -18,5 +18,5 @@ $ underline(f' : NN -> RR) \ $ dot \ dots \ ast \ tilde \ star $ --- -// Error: 1:3 expected dollar sign +// Error: 1-2 unclosed delimiter $a diff --git a/tests/typ/meta/link.typ b/tests/typ/meta/link.typ index 36f88f90..37eba6b7 100644 --- a/tests/typ/meta/link.typ +++ b/tests/typ/meta/link.typ @@ -35,7 +35,7 @@ https://example.com/) --- // Verify that opening brackets without closing brackets throw an error. -// Error: 22-22 expected closing bracket in link +// Error: 1-22 automatic links cannot contain unbalanced brackets, use the `link` function instead https://exam(ple.com/ --- diff --git a/tests/typ/text/emphasis.typ b/tests/typ/text/emphasis.typ index 0191ac87..fd04c8e7 100644 --- a/tests/typ/text/emphasis.typ +++ b/tests/typ/text/emphasis.typ @@ -27,17 +27,17 @@ Normal *Medium* and *#[*Bold*]* --- -// Error: 13 expected underscore +// Error: 6-7 unclosed delimiter #box[_Scoped] to body. --- // Ends at paragraph break. -// Error: 7 expected underscore +// Error: 1-2 unclosed delimiter _Hello World --- -// Error: 26 expected star -// Error: 26 expected underscore +// Error: 11-12 unclosed delimiter +// Error: 3-4 unclosed delimiter #[_Cannot *be interleaved] diff --git a/tests/typ/text/escape.typ b/tests/typ/text/escape.typ index e7ec9023..8e557918 100644 --- a/tests/typ/text/escape.typ +++ b/tests/typ/text/escape.typ @@ -32,5 +32,5 @@ let f() , ; : | + - /= == 12 "string" --- // Unterminated. -// Error: 6 expected closing brace +// Error: 1-6 unclosed unicode escape sequence \u{41[*Bold*] diff --git a/tests/typ/text/raw.typ b/tests/typ/text/raw.typ index 1040151c..8cf5ee7e 100644 --- a/tests/typ/text/raw.typ +++ b/tests/typ/text/raw.typ @@ -55,5 +55,5 @@ The keyword ```rust let```. --- // Unterminated. -// Error: 2:1 expected 1 backtick +// Error: 1-2:1 unclosed raw text `endless