1use crate::ast::token_range::WithTokenSpan;
8use crate::ast::{Name, SeparatedList};
9use crate::data::DiagnosticResult;
10use crate::syntax::common::ParseResult;
11use crate::syntax::names::parse_name;
12use crate::syntax::Kind::{Comma, Identifier};
13use crate::syntax::{kind_str, kinds_error, Kind, TokenAccess};
14use crate::Diagnostic;
15use vhdl_lang::syntax::parser::ParsingContext;
16
17fn skip_extraneous_tokens(ctx: &mut ParsingContext<'_>, separator: Kind) {
20 if let Some(separator_tok) = ctx.stream.pop_if_kind(separator) {
21 let start_pos = ctx.stream.get_pos(separator_tok);
22 let mut end_pos = start_pos;
23 while let Some(separator_tok) = ctx.stream.pop_if_kind(separator) {
24 end_pos = ctx.stream.get_pos(separator_tok)
25 }
26 ctx.diagnostics.push(Diagnostic::syntax_error(
27 start_pos.combine(end_pos),
28 format!("Extraneous '{}'", kind_str(separator)),
29 ));
30 }
31}
32
33pub fn parse_list_with_separator_or_recover<F, T>(
42 ctx: &mut ParsingContext<'_>,
43 separator: Kind,
44 parse_fn: F,
45 recover_token: Option<Kind>,
46 start_token: Option<Kind>,
47) -> DiagnosticResult<SeparatedList<T>>
48where
49 F: Fn(&mut ParsingContext<'_>) -> ParseResult<T>,
50{
51 let mut items = vec![];
52 let mut tokens = vec![];
53 loop {
54 match parse_fn(ctx) {
55 Ok(item) => items.push(item),
56 Err(err) => {
57 if let Some(tok) = recover_token {
58 ctx.stream
59 .skip_until(|kind| kind == separator || kind == tok)?;
60 ctx.diagnostics.push(err);
61 } else {
62 return Err(err);
63 }
64 }
65 }
66 if let Some(separator_tok) = ctx.stream.pop_if_kind(separator) {
67 skip_extraneous_tokens(ctx, separator);
68 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 }
78 } else {
79 break;
80 }
81 }
82 Ok(SeparatedList { items, tokens })
83}
84
85pub fn parse_name_list(ctx: &mut ParsingContext<'_>) -> DiagnosticResult<Vec<WithTokenSpan<Name>>> {
86 Ok(parse_list_with_separator_or_recover(ctx, Comma, parse_name, None, Some(Identifier))?.items)
87}
88
89#[cfg(test)]
90mod test {
91 use crate::ast::SeparatedList;
92 use crate::syntax::names::{parse_association_element, parse_name};
93 use crate::syntax::separated_list::{parse_list_with_separator_or_recover, parse_name_list};
94 use crate::syntax::test::Code;
95 use crate::syntax::Kind;
96 use crate::syntax::Kind::{Identifier, RightPar};
97 use crate::Diagnostic;
98
99 #[test]
100 pub fn test_error_on_empty_list() {
101 let code = Code::new("");
102 let (res, diagnostics) = code.with_partial_stream_diagnostics(parse_name_list);
103 assert_eq!(
104 res,
105 Err(Diagnostic::syntax_error(code.eof_pos(), "Unexpected EOF"))
106 );
107 assert!(diagnostics.is_empty());
108 }
109
110 #[test]
111 pub fn parse_single_element_list() {
112 let code = Code::new("abc");
113 assert_eq!(
114 code.parse_ok_no_diagnostics(parse_name_list),
115 vec![code.s1("abc").name()]
116 )
117 }
118
119 #[test]
120 fn parse_list_with_many_names() {
121 let code = Code::new("work.foo, lib.bar.all");
122 assert_eq!(
123 code.parse_ok_no_diagnostics(parse_name_list),
124 vec![code.s1("work.foo").name(), code.s1("lib.bar.all").name()]
125 )
126 }
127
128 #[test]
129 fn parse_extraneous_single_separators() {
130 let code = Code::new("a,,b,c");
131 let (res, diag) = code.with_stream_diagnostics(parse_name_list);
132 assert_eq!(
133 res,
134 vec![
135 code.s1("a").name(),
136 code.s1("b").name(),
137 code.s1("c").name()
138 ]
139 );
140 assert_eq!(
141 diag,
142 vec![Diagnostic::syntax_error(
143 code.s(",", 2).pos(),
144 "Extraneous ','"
145 )]
146 )
147 }
148
149 #[test]
150 fn parse_extraneous_multiple_separators() {
151 let code = Code::new("a,,,,b,c");
152 let (res, diag) = code.with_stream_diagnostics(parse_name_list);
153 assert_eq!(
154 res,
155 vec![
156 code.s1("a").name(),
157 code.s1("b").name(),
158 code.s1("c").name()
159 ]
160 );
161 assert_eq!(
162 diag,
163 vec![Diagnostic::syntax_error(
164 code.s(",,,", 2).pos(),
165 "Extraneous ','"
166 )]
167 )
168 }
169
170 #[test]
171 fn parse_recoverable_list() {
172 let code = Code::new("a => b,c => d, e =>)");
173 let (res, diag) = code.with_stream_diagnostics(|ctx| {
174 let res = parse_list_with_separator_or_recover(
175 ctx,
176 Kind::Comma,
177 parse_association_element,
178 Some(RightPar),
179 Some(Identifier),
180 );
181 ctx.stream.skip();
182 res
183 });
184 assert_eq!(
185 res,
186 SeparatedList {
187 items: vec![
188 code.s1("a => b").association_element(),
189 code.s1("c => d").association_element()
190 ],
191 tokens: vec![code.s(",", 1).token(), code.s(",", 2).token(),],
192 }
193 );
194 assert_eq!(
195 diag,
196 vec![Diagnostic::syntax_error(
197 code.s1(")"),
198 "Expected {expression}"
199 )]
200 );
201 }
202
203 #[test]
204 fn parse_list_with_erroneous_elements() {
205 let code = Code::new("1,c,d");
206 let diag = code
207 .parse(parse_name_list)
208 .0
209 .expect_err("Should not parse OK");
210 assert_eq!(
211 diag,
212 Diagnostic::syntax_error(
213 code.s1("1"),
214 "Expected '{identifier}', '{character}', '{string}' or 'all'"
215 )
216 );
217 }
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 c_pos.range.start.character += 1;
254 c_pos.range.end.character += 2;
255
256 assert_eq!(diag, vec![Diagnostic::syntax_error(c_pos, "Expected ','")]);
257 }
258}