use crate::ast::token_range::WithTokenSpan;
use crate::ast::{Name, SeparatedList};
use crate::data::DiagnosticResult;
use crate::syntax::common::ParseResult;
use crate::syntax::names::parse_name;
use crate::syntax::Kind::{Comma, Identifier};
use crate::syntax::{kind_str, kinds_error, Kind, TokenAccess};
use crate::Diagnostic;
use vhdl_lang::syntax::parser::ParsingContext;
fn skip_extraneous_tokens(ctx: &mut ParsingContext<'_>, separator: Kind) {
if let Some(separator_tok) = ctx.stream.pop_if_kind(separator) {
let start_pos = ctx.stream.get_pos(separator_tok);
let mut end_pos = start_pos;
while let Some(separator_tok) = ctx.stream.pop_if_kind(separator) {
end_pos = ctx.stream.get_pos(separator_tok)
}
ctx.diagnostics.push(Diagnostic::syntax_error(
start_pos.combine(end_pos),
format!("Extraneous '{}'", kind_str(separator)),
));
}
}
pub fn parse_list_with_separator_or_recover<F, T>(
ctx: &mut ParsingContext<'_>,
separator: Kind,
parse_fn: F,
recover_token: Option<Kind>,
start_token: Option<Kind>,
) -> DiagnosticResult<SeparatedList<T>>
where
F: Fn(&mut ParsingContext<'_>) -> ParseResult<T>,
{
let mut items = vec![];
let mut tokens = vec![];
loop {
match parse_fn(ctx) {
Ok(item) => items.push(item),
Err(err) => {
if let Some(tok) = recover_token {
ctx.stream
.skip_until(|kind| kind == separator || kind == tok)?;
ctx.diagnostics.push(err);
} else {
return Err(err);
}
}
}
if let Some(separator_tok) = ctx.stream.pop_if_kind(separator) {
skip_extraneous_tokens(ctx, separator);
tokens.push(separator_tok);
} else if let Some(kind) = start_token {
if ctx.stream.next_kind_is(kind) {
ctx.diagnostics.push(kinds_error(
ctx.stream.pos_before(ctx.stream.peek().unwrap()),
&[separator],
));
} else {
break;
}
} else {
break;
}
}
Ok(SeparatedList { items, tokens })
}
pub fn parse_name_list(ctx: &mut ParsingContext<'_>) -> DiagnosticResult<Vec<WithTokenSpan<Name>>> {
Ok(parse_list_with_separator_or_recover(ctx, Comma, parse_name, None, Some(Identifier))?.items)
}
#[cfg(test)]
mod test {
use crate::ast::SeparatedList;
use crate::syntax::names::{parse_association_element, parse_name};
use crate::syntax::separated_list::{parse_list_with_separator_or_recover, parse_name_list};
use crate::syntax::test::Code;
use crate::syntax::Kind;
use crate::syntax::Kind::{Identifier, RightPar};
use crate::Diagnostic;
#[test]
pub fn test_error_on_empty_list() {
let code = Code::new("");
let (res, diagnostics) = code.with_partial_stream_diagnostics(parse_name_list);
assert_eq!(
res,
Err(Diagnostic::syntax_error(code.eof_pos(), "Unexpected EOF"))
);
assert!(diagnostics.is_empty());
}
#[test]
pub fn parse_single_element_list() {
let code = Code::new("abc");
assert_eq!(
code.parse_ok_no_diagnostics(parse_name_list),
vec![code.s1("abc").name()]
)
}
#[test]
fn parse_list_with_many_names() {
let code = Code::new("work.foo, lib.bar.all");
assert_eq!(
code.parse_ok_no_diagnostics(parse_name_list),
vec![code.s1("work.foo").name(), code.s1("lib.bar.all").name()]
)
}
#[test]
fn parse_extraneous_single_separators() {
let code = Code::new("a,,b,c");
let (res, diag) = code.with_stream_diagnostics(parse_name_list);
assert_eq!(
res,
vec![
code.s1("a").name(),
code.s1("b").name(),
code.s1("c").name()
]
);
assert_eq!(
diag,
vec![Diagnostic::syntax_error(
code.s(",", 2).pos(),
"Extraneous ','"
)]
)
}
#[test]
fn parse_extraneous_multiple_separators() {
let code = Code::new("a,,,,b,c");
let (res, diag) = code.with_stream_diagnostics(parse_name_list);
assert_eq!(
res,
vec![
code.s1("a").name(),
code.s1("b").name(),
code.s1("c").name()
]
);
assert_eq!(
diag,
vec![Diagnostic::syntax_error(
code.s(",,,", 2).pos(),
"Extraneous ','"
)]
)
}
#[test]
fn parse_recoverable_list() {
let code = Code::new("a => b,c => d, e =>)");
let (res, diag) = code.with_stream_diagnostics(|ctx| {
let res = parse_list_with_separator_or_recover(
ctx,
Kind::Comma,
parse_association_element,
Some(RightPar),
Some(Identifier),
);
ctx.stream.skip();
res
});
assert_eq!(
res,
SeparatedList {
items: vec![
code.s1("a => b").association_element(),
code.s1("c => d").association_element()
],
tokens: vec![code.s(",", 1).token(), code.s(",", 2).token(),],
}
);
assert_eq!(
diag,
vec![Diagnostic::syntax_error(
code.s1(")"),
"Expected {expression}"
)]
);
}
#[test]
fn parse_list_with_erroneous_elements() {
let code = Code::new("1,c,d");
let diag = code
.parse(parse_name_list)
.0
.expect_err("Should not parse OK");
assert_eq!(
diag,
Diagnostic::syntax_error(
code.s1("1"),
"Expected '{identifier}', '{character}', '{string}' or 'all'"
)
);
}
#[test]
fn parse_list_with_missing_separator() {
let code = Code::new("a ,b, c d, e)");
let (res, diag) = code.with_stream_diagnostics(|ctx| {
let res = parse_list_with_separator_or_recover(
ctx,
Kind::Comma,
parse_name,
Some(RightPar),
Some(Identifier),
);
ctx.stream.skip();
res
});
assert_eq!(
res,
SeparatedList {
items: vec![
code.s1("a").name(),
code.s1("b").name(),
code.s1("c").name(),
code.s1("d").name(),
code.s1("e").name()
],
tokens: vec![
code.s(",", 1).token(),
code.s(",", 2).token(),
code.s(",", 3).token()
],
}
);
let mut c_pos = code.s1("c").pos().end_pos();
c_pos.range.start.character += 1;
c_pos.range.end.character += 2;
assert_eq!(diag, vec![Diagnostic::syntax_error(c_pos, "Expected ','")]);
}
}