vhdl_lang/syntax/
separated_list.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this file,
3// You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com
6
7use 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
17/// Skip extraneous tokens of kind `separator`.
18/// When there are any extra tokens of that kind, mark all the positions of these tokens as erroneous
19fn 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
33/// Parses a list that is separated by a single token, denoted by the `separator`
34/// However, when supplied with a `recover_token` will skip until either the separator
35/// 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.
41pub 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        // 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!(diag, vec![Diagnostic::syntax_error(c_pos, "Expected ','")]);
257    }
258}