Skip to content

Commit 950fe46

Browse files
committed
Feat: Improve robustness of separated list parsing for enhanced completions
1 parent 5c79bc8 commit 950fe46

File tree

2 files changed

+66
-21
lines changed

2 files changed

+66
-21
lines changed

vhdl_lang/src/syntax/names.rs

+1
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,7 @@ pub fn parse_association_list_no_leftpar(
240240
Comma,
241241
parse_association_element,
242242
Some(RightPar),
243+
Some(Identifier),
243244
)?;
244245
let right_par = ctx.stream.expect_kind(RightPar)?;
245246
Ok((list, right_par))

vhdl_lang/src/syntax/separated_list.rs

+65-21
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use crate::ast::{Name, SeparatedList};
99
use crate::data::DiagnosticResult;
1010
use crate::syntax::common::ParseResult;
1111
use crate::syntax::names::parse_name;
12-
use crate::syntax::Kind::Comma;
13-
use crate::syntax::{kind_str, Kind, TokenAccess};
12+
use crate::syntax::Kind::{Comma, Identifier};
13+
use crate::syntax::{kind_str, kinds_error, Kind, TokenAccess};
1414
use crate::Diagnostic;
1515
use vhdl_lang::syntax::parser::ParsingContext;
1616

@@ -30,29 +30,20 @@ fn skip_extraneous_tokens(ctx: &mut ParsingContext<'_>, separator: Kind) {
3030
}
3131
}
3232

33-
/// Parses a list of the form
34-
/// `element { separator element }`
35-
/// where `element` is an AST element and `separator` is a token of some `ast::Kind`.
36-
/// The returned list retains information of the whereabouts of the separator tokens.
37-
pub fn parse_list_with_separator<F, T>(
38-
ctx: &mut ParsingContext<'_>,
39-
separator: Kind,
40-
parse_fn: F,
41-
) -> DiagnosticResult<SeparatedList<T>>
42-
where
43-
F: Fn(&mut ParsingContext<'_>) -> ParseResult<T>,
44-
{
45-
parse_list_with_separator_or_recover(ctx, separator, parse_fn, None)
46-
}
47-
48-
/// Same as `parse_list_with_separator`.
33+
/// Parses a list that is separated by a single token, denoted by the `separator`
4934
/// However, when supplied with a `recover_token` will skip until either the separator
5035
/// or the recover token is found.
36+
///
37+
/// * recover_token: When supplied with a `recover_token`, the `parse_fn` fails and will forward
38+
/// the stream until this token is seen
39+
/// * start_token: The token-kind that `parse_fn` starts with.
40+
/// If not `None`, this is used to detect missing separators and continue parsing.
5141
pub fn parse_list_with_separator_or_recover<F, T>(
5242
ctx: &mut ParsingContext<'_>,
5343
separator: Kind,
5444
parse_fn: F,
5545
recover_token: Option<Kind>,
46+
start_token: Option<Kind>,
5647
) -> DiagnosticResult<SeparatedList<T>>
5748
where
5849
F: Fn(&mut ParsingContext<'_>) -> ParseResult<T>,
@@ -75,6 +66,15 @@ where
7566
if let Some(separator_tok) = ctx.stream.pop_if_kind(separator) {
7667
skip_extraneous_tokens(ctx, separator);
7768
tokens.push(separator_tok);
69+
} else if let Some(kind) = start_token {
70+
if ctx.stream.next_kind_is(kind) {
71+
ctx.diagnostics.push(kinds_error(
72+
ctx.stream.pos_before(ctx.stream.peek().unwrap()),
73+
&[separator],
74+
));
75+
} else {
76+
break;
77+
}
7878
} else {
7979
break;
8080
}
@@ -83,17 +83,17 @@ where
8383
}
8484

8585
pub fn parse_name_list(ctx: &mut ParsingContext<'_>) -> DiagnosticResult<Vec<WithTokenSpan<Name>>> {
86-
Ok(parse_list_with_separator(ctx, Comma, parse_name)?.items)
86+
Ok(parse_list_with_separator_or_recover(ctx, Comma, parse_name, None, Some(Identifier))?.items)
8787
}
8888

8989
#[cfg(test)]
9090
mod test {
9191
use crate::ast::SeparatedList;
92-
use crate::syntax::names::parse_association_element;
92+
use crate::syntax::names::{parse_association_element, parse_name};
9393
use crate::syntax::separated_list::{parse_list_with_separator_or_recover, parse_name_list};
9494
use crate::syntax::test::Code;
9595
use crate::syntax::Kind;
96-
use crate::syntax::Kind::RightPar;
96+
use crate::syntax::Kind::{Identifier, RightPar};
9797
use crate::Diagnostic;
9898

9999
#[test]
@@ -176,6 +176,7 @@ mod test {
176176
Kind::Comma,
177177
parse_association_element,
178178
Some(RightPar),
179+
Some(Identifier),
179180
);
180181
ctx.stream.skip();
181182
res
@@ -214,4 +215,47 @@ mod test {
214215
)
215216
);
216217
}
218+
219+
#[test]
220+
fn parse_list_with_missing_separator() {
221+
let code = Code::new("a ,b, c d, e)");
222+
let (res, diag) = code.with_stream_diagnostics(|ctx| {
223+
let res = parse_list_with_separator_or_recover(
224+
ctx,
225+
Kind::Comma,
226+
parse_name,
227+
Some(RightPar),
228+
Some(Identifier),
229+
);
230+
ctx.stream.skip();
231+
res
232+
});
233+
assert_eq!(
234+
res,
235+
SeparatedList {
236+
items: vec![
237+
code.s1("a").name(),
238+
code.s1("b").name(),
239+
code.s1("c").name(),
240+
code.s1("d").name(),
241+
code.s1("e").name()
242+
],
243+
tokens: vec![
244+
code.s(",", 1).token(),
245+
code.s(",", 2).token(),
246+
code.s(",", 3).token()
247+
],
248+
}
249+
);
250+
251+
let mut c_pos = code.s1("c").pos().end_pos();
252+
// single char position right after the 'c' character
253+
c_pos.range.start.character += 1;
254+
c_pos.range.end.character += 2;
255+
256+
assert_eq!(
257+
diag,
258+
vec![Diagnostic::syntax_error(c_pos, "Expected ','")]
259+
);
260+
}
217261
}

0 commit comments

Comments
 (0)