summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/syntax/lexer.rs39
-rw-r--r--src/syntax/parser.rs46
-rw-r--r--src/syntax/reparser.rs8
-rw-r--r--tests/typ/compiler/array.typ4
-rw-r--r--tests/typ/compiler/block.typ2
-rw-r--r--tests/typ/compiler/call.typ7
-rw-r--r--tests/typ/compiler/let.typ2
-rw-r--r--tests/typ/compiler/ops-prec.typ2
-rw-r--r--tests/typ/compiler/string.typ2
-rw-r--r--tests/typ/math/syntax.typ2
-rw-r--r--tests/typ/meta/link.typ2
-rw-r--r--tests/typ/text/emphasis.typ8
-rw-r--r--tests/typ/text/escape.typ2
-rw-r--r--tests/typ/text/raw.typ2
14 files changed, 65 insertions, 63 deletions
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<EcoString>) -> 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<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();
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