Rework delimiter errors

Fixes #572
This commit is contained in:
Laurenz 2023-05-24 16:58:51 +02:00
parent b9d03be975
commit 017027bf39
14 changed files with 65 additions and 63 deletions

View File

@ -80,12 +80,6 @@ impl Lexer<'_> {
self.error = Some((message.into(), ErrorPos::Full)); self.error = Some((message.into(), ErrorPos::Full));
SyntaxKind::Error SyntaxKind::Error
} }
/// Construct a positioned syntax error.
fn error_at_end(&mut self, message: impl Into<EcoString>) -> SyntaxKind {
self.error = Some((message.into(), ErrorPos::End));
SyntaxKind::Error
}
} }
/// Shared. /// Shared.
@ -209,7 +203,7 @@ impl Lexer<'_> {
if self.s.eat_if("u{") { if self.s.eat_if("u{") {
let hex = self.s.eat_while(char::is_ascii_alphanumeric); let hex = self.s.eat_while(char::is_ascii_alphanumeric);
if !self.s.eat_if('}') { 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) if u32::from_str_radix(hex, 16)
@ -251,20 +245,15 @@ impl Lexer<'_> {
} }
if found != backticks { if found != backticks {
let remaining = backticks - found; return self.error("unclosed raw text");
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)
});
} }
SyntaxKind::Raw SyntaxKind::Raw
} }
fn link(&mut self) -> SyntaxKind { fn link(&mut self) -> SyntaxKind {
let mut bracket_stack = Vec::new(); let mut brackets = Vec::new();
#[rustfmt::skip] #[rustfmt::skip]
self.s.eat_while(|c: char| { self.s.eat_while(|c: char| {
match c { match c {
@ -275,20 +264,24 @@ impl Lexer<'_> {
| ',' | '-' | '.' | '/' | ':' | ';' | '=' | ',' | '-' | '.' | '/' | ':' | ';' | '='
| '?' | '@' | '_' | '~' | '\'' => true, | '?' | '@' | '_' | '~' | '\'' => true,
'[' => { '[' => {
bracket_stack.push(SyntaxKind::LeftBracket); brackets.push(SyntaxKind::LeftBracket);
true true
} }
'(' => { '(' => {
bracket_stack.push(SyntaxKind::LeftParen); brackets.push(SyntaxKind::LeftParen);
true true
} }
']' => bracket_stack.pop() == Some(SyntaxKind::LeftBracket), ']' => brackets.pop() == Some(SyntaxKind::LeftBracket),
')' => bracket_stack.pop() == Some(SyntaxKind::LeftParen), ')' => brackets.pop() == Some(SyntaxKind::LeftParen),
_ => false, _ => 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. // Don't include the trailing characters likely to be part of text.
@ -328,7 +321,7 @@ impl Lexer<'_> {
} }
if !self.s.eat_if('>') { if !self.s.eat_if('>') {
return self.error_at_end("expected closing angle bracket"); return self.error("unclosed label");
} }
SyntaxKind::Label SyntaxKind::Label
@ -620,7 +613,7 @@ impl Lexer<'_> {
}); });
if !self.s.eat_if('"') { if !self.s.eat_if('"') {
return self.error_at_end("expected quote"); return self.error("unclosed string");
} }
SyntaxKind::Str SyntaxKind::Str

View File

@ -71,7 +71,7 @@ pub(super) fn reparse_markup(
match p.current() { match p.current() {
SyntaxKind::LeftBracket => *nesting += 1, SyntaxKind::LeftBracket => *nesting += 1,
SyntaxKind::RightBracket if *nesting > 0 => *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::Parbreak)
|| p.at(SyntaxKind::RightBracket) || p.at(SyntaxKind::RightBracket)
}); });
p.expect(SyntaxKind::Star); p.expect_closing_delimiter(m, SyntaxKind::Star);
p.wrap(m, SyntaxKind::Strong); p.wrap(m, SyntaxKind::Strong);
} }
@ -153,7 +153,7 @@ fn emph(p: &mut Parser) {
|| p.at(SyntaxKind::Parbreak) || p.at(SyntaxKind::Parbreak)
|| p.at(SyntaxKind::RightBracket) || p.at(SyntaxKind::RightBracket)
}); });
p.expect(SyntaxKind::Underscore); p.expect_closing_delimiter(m, SyntaxKind::Underscore);
p.wrap(m, SyntaxKind::Emph); p.wrap(m, SyntaxKind::Emph);
} }
@ -220,15 +220,15 @@ fn equation(p: &mut Parser) {
let m = p.marker(); let m = p.marker();
p.enter(LexMode::Math); p.enter(LexMode::Math);
p.assert(SyntaxKind::Dollar); p.assert(SyntaxKind::Dollar);
math(p, |kind| kind == SyntaxKind::Dollar); math(p, |p| p.at(SyntaxKind::Dollar));
p.expect(SyntaxKind::Dollar); p.expect_closing_delimiter(m, SyntaxKind::Dollar);
p.exit(); p.exit();
p.wrap(m, SyntaxKind::Equation); 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(); let m = p.marker();
while !p.eof() && !stop(p.current()) { while !p.eof() && !stop(p) {
let prev = p.prev_end(); let prev = p.prev_end();
math_expr(p); math_expr(p);
if !p.progress(prev) { if !p.progress(prev) {
@ -514,22 +514,18 @@ fn maybe_wrap_in_math(p: &mut Parser, arg: Marker, named: Option<Marker>) {
} }
} }
fn code(p: &mut Parser, stop: impl FnMut(SyntaxKind) -> bool) { fn code(p: &mut Parser, stop: impl FnMut(&Parser) -> bool) {
let m = p.marker(); let m = p.marker();
code_exprs(p, stop); code_exprs(p, stop);
p.wrap(m, SyntaxKind::Code); p.wrap(m, SyntaxKind::Code);
} }
fn code_exprs(p: &mut Parser, mut stop: impl FnMut(SyntaxKind) -> bool) { fn code_exprs(p: &mut Parser, mut stop: impl FnMut(&Parser) -> bool) {
while !p.eof() && !stop(p.current()) { while !p.eof() && !stop(p) {
p.stop_at_newline(true); p.stop_at_newline(true);
let prev = p.prev_end(); let prev = p.prev_end();
code_expr(p); code_expr(p);
if p.progress(prev) if p.progress(prev) && !p.eof() && !stop(p) && !p.eat_if(SyntaxKind::Semicolon) {
&& !p.eof()
&& !stop(p.current())
&& !p.eat_if(SyntaxKind::Semicolon)
{
p.expected("semicolon or line break"); p.expected("semicolon or line break");
} }
p.unstop(); p.unstop();
@ -725,8 +721,12 @@ fn code_block(p: &mut Parser) {
p.enter(LexMode::Code); p.enter(LexMode::Code);
p.stop_at_newline(false); p.stop_at_newline(false);
p.assert(SyntaxKind::LeftBrace); p.assert(SyntaxKind::LeftBrace);
code(p, |kind| kind == SyntaxKind::RightBrace); code(p, |p| {
p.expect(SyntaxKind::RightBrace); p.at(SyntaxKind::RightBrace)
|| p.at(SyntaxKind::RightBracket)
|| p.at(SyntaxKind::RightParen)
});
p.expect_closing_delimiter(m, SyntaxKind::RightBrace);
p.exit(); p.exit();
p.unstop(); p.unstop();
p.wrap(m, SyntaxKind::CodeBlock); p.wrap(m, SyntaxKind::CodeBlock);
@ -737,7 +737,7 @@ fn content_block(p: &mut Parser) {
p.enter(LexMode::Markup); p.enter(LexMode::Markup);
p.assert(SyntaxKind::LeftBracket); p.assert(SyntaxKind::LeftBracket);
markup(p, true, 0, |p| p.at(SyntaxKind::RightBracket)); markup(p, true, 0, |p| p.at(SyntaxKind::RightBracket));
p.expect(SyntaxKind::RightBracket); p.expect_closing_delimiter(m, SyntaxKind::RightBracket);
p.exit(); p.exit();
p.wrap(m, SyntaxKind::ContentBlock); 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 { fn collection(p: &mut Parser, keyed: bool) -> SyntaxKind {
p.stop_at_newline(false); p.stop_at_newline(false);
let m = p.marker();
p.assert(SyntaxKind::LeftParen); p.assert(SyntaxKind::LeftParen);
let mut count = 0; 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(); p.unstop();
if parenthesized && count == 1 { if parenthesized && count == 1 {
@ -1586,6 +1588,12 @@ impl<'s> Parser<'s> {
at 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) { fn expected(&mut self, thing: &str) {
self.unskip(); self.unskip();
if self if self

View File

@ -306,15 +306,15 @@ mod tests {
test("Hello #{ x + 1 }!", 9..10, "abc", true); test("Hello #{ x + 1 }!", 9..10, "abc", true);
test("A#{}!", 3..3, "\"", false); test("A#{}!", 3..3, "\"", false);
test("#{ [= x] }!", 5..5, "=", true); test("#{ [= x] }!", 5..5, "=", true);
test("#[[]]", 3..3, "\\", false); test("#[[]]", 3..3, "\\", true);
test("#[[ab]]", 4..5, "\\", false); test("#[[ab]]", 4..5, "\\", true);
test("#{}}", 2..2, "{", false); test("#{}}", 2..2, "{", false);
test("A: #[BC]", 6..6, "{", true); 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("A: #[BC]", 6..6, "#{}", true);
test("#{\"ab\"}A", 5..5, "c", true); test("#{\"ab\"}A", 5..5, "c", true);
test("#{\"ab\"}A", 5..6, "c", false); 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#{call(); abc}b", 8..8, "[]", true);
test("a #while x {\n g(x) \n} b", 12..12, "//", true); test("a #while x {\n g(x) \n} b", 12..12, "//", true);
test("a#[]b", 3..3, "[hey]", true); test("a#[]b", 3..3, "[hey]", true);

View File

@ -256,10 +256,10 @@
#(1, 2, 3).at(-4) #(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 // Error: 4-6 unexpected end of block comment

View File

@ -138,7 +138,7 @@
} }
--- ---
// Error: 3 expected closing brace // Error: 2-3 unclosed delimiter
#{ #{
--- ---

View File

@ -94,13 +94,14 @@
#func((x):1) #func((x):1)
--- ---
// Error: 2:1 expected closing bracket // Error: 6-7 unclosed delimiter
#func[`a]` #func[`a]`
--- ---
// Error: 8 expected closing paren // Error: 7-8 unclosed delimiter
#{func(} #{func(}
--- ---
// Error: 2:1 expected quote // Error: 6-7 unclosed delimiter
// Error: 1:7-2:1 unclosed string
#func("] #func("]

View File

@ -227,7 +227,7 @@ Three
// Terminated by semicolon even though we are in a paren group. // Terminated by semicolon even though we are in a paren group.
// Error: 18 expected expression // Error: 18 expected expression
// Error: 18 expected closing paren // Error: 11-12 unclosed delimiter
#let v5 = (1, 2 + ; Five #let v5 = (1, 2 + ; Five
// Error: 9-13 expected identifier, found boolean // Error: 9-13 expected identifier, found boolean

View File

@ -32,5 +32,5 @@
#test((1), 1) #test((1), 1)
#test((1+2)*-3, -9) #test((1+2)*-3, -9)
// Error: 14 expected closing paren // Error: 8-9 unclosed delimiter
#test({(1 + 1}, 2) #test({(1 + 1}, 2)

View File

@ -204,5 +204,5 @@
#test("a123c".split(regex("\d+")), ("a", "c")) #test("a123c".split(regex("\d+")), ("a", "c"))
--- ---
// Error: 2:1 expected quote // Error: 2-2:1 unclosed string
#"hello\" #"hello\"

View File

@ -18,5 +18,5 @@ $ underline(f' : NN -> RR) \
$ dot \ dots \ ast \ tilde \ star $ $ dot \ dots \ ast \ tilde \ star $
--- ---
// Error: 1:3 expected dollar sign // Error: 1-2 unclosed delimiter
$a $a

View File

@ -35,7 +35,7 @@ https://example.com/)
--- ---
// Verify that opening brackets without closing brackets throw an error. // 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/ https://exam(ple.com/
--- ---

View File

@ -27,17 +27,17 @@ Normal
*Medium* and *#[*Bold*]* *Medium* and *#[*Bold*]*
--- ---
// Error: 13 expected underscore // Error: 6-7 unclosed delimiter
#box[_Scoped] to body. #box[_Scoped] to body.
--- ---
// Ends at paragraph break. // Ends at paragraph break.
// Error: 7 expected underscore // Error: 1-2 unclosed delimiter
_Hello _Hello
World World
--- ---
// Error: 26 expected star // Error: 11-12 unclosed delimiter
// Error: 26 expected underscore // Error: 3-4 unclosed delimiter
#[_Cannot *be interleaved] #[_Cannot *be interleaved]

View File

@ -32,5 +32,5 @@ let f() , ; : | + - /= == 12 "string"
--- ---
// Unterminated. // Unterminated.
// Error: 6 expected closing brace // Error: 1-6 unclosed unicode escape sequence
\u{41[*Bold*] \u{41[*Bold*]

View File

@ -55,5 +55,5 @@ The keyword ```rust let```.
--- ---
// Unterminated. // Unterminated.
// Error: 2:1 expected 1 backtick // Error: 1-2:1 unclosed raw text
`endless `endless