use super::common::check_end_identifier_mismatch;
use super::common::ParseResult;
use super::names::{parse_identifier_list, parse_name};
use super::tokens::{Kind::*, TokenSpan};
use crate::ast::token_range::WithTokenSpan;
use crate::ast::*;
use crate::syntax::parser::ParsingContext;
use crate::syntax::recover;
use crate::syntax::recover::expect_semicolon;
use crate::syntax::separated_list::parse_name_list;
use itertools::Itertools;
#[derive(PartialEq, Debug)]
pub enum DeclarationOrReference {
Declaration(ContextDeclaration),
Reference(ContextReference),
}
pub fn parse_library_clause(ctx: &mut ParsingContext<'_>) -> ParseResult<LibraryClause> {
let library_token = ctx.stream.expect_kind(Library)?;
let name_list = parse_identifier_list(ctx)?
.into_iter()
.map(WithRef::new)
.collect_vec();
let semi_token = recover::expect_semicolon_or_last(ctx);
Ok(LibraryClause {
span: TokenSpan::new(library_token, semi_token),
name_list,
})
}
pub fn parse_use_clause(ctx: &mut ParsingContext<'_>) -> ParseResult<WithTokenSpan<UseClause>> {
let use_token = ctx.stream.expect_kind(Use)?;
let name_list = parse_name_list(ctx)?;
let token_id = recover::expect_semicolon(ctx).unwrap_or(ctx.stream.get_last_token_id());
Ok(WithTokenSpan::new(
UseClause {
span: TokenSpan::new(use_token, token_id),
name_list,
},
TokenSpan::new(use_token, token_id),
))
}
pub fn parse_context_reference(ctx: &mut ParsingContext<'_>) -> ParseResult<ContextReference> {
let context_token = ctx.stream.expect_kind(Context)?;
let name_list = parse_name_list(ctx)?;
let semi_token = recover::expect_semicolon_or_last(ctx);
Ok(ContextReference {
span: TokenSpan::new(context_token, semi_token),
name_list,
})
}
pub fn parse_context(ctx: &mut ParsingContext<'_>) -> ParseResult<DeclarationOrReference> {
let context_token = ctx.stream.expect_kind(Context)?;
let name = parse_name(ctx)?;
if ctx.stream.skip_if_kind(Is) {
let mut items = Vec::with_capacity(16);
let end_ident;
let end_token;
loop {
let token = ctx.stream.peek_expect()?;
try_init_token_kind!(
token,
Library => items.push(ContextItem::Library(parse_library_clause(ctx)?)),
Use => items.push(ContextItem::Use(parse_use_clause(ctx)?.item)),
Context => items.push(ContextItem::Context(parse_context_reference(ctx)?)),
End => {
end_token = ctx.stream.get_current_token_id();
ctx.stream.skip();
ctx.stream.pop_if_kind(Context);
end_ident = ctx.stream.pop_optional_ident();
expect_semicolon(ctx);
break;
}
)
}
let ident = WithDecl::new(to_simple_name(ctx, name)?);
let semicolon = ctx.stream.get_last_token_id();
Ok(DeclarationOrReference::Declaration(ContextDeclaration {
span: TokenSpan::new(context_token, semicolon),
end_ident_pos: check_end_identifier_mismatch(ctx, &ident.tree, end_ident),
ident,
end_token,
items,
}))
} else {
let mut items = vec![name];
while ctx.stream.pop_if_kind(Comma).is_some() {
items.push(parse_name(ctx)?);
}
let semi_token = recover::expect_semicolon_or_last(ctx);
Ok(DeclarationOrReference::Reference(ContextReference {
span: TokenSpan::new(context_token, semi_token),
name_list: items,
}))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::data::Diagnostic;
use crate::syntax::test::{token_to_string, Code};
use crate::HasTokenSpan;
use pretty_assertions::assert_eq;
#[test]
fn test_library_clause_single_name() {
let code = Code::new("library foo;");
assert_eq!(
code.with_stream_no_diagnostics(parse_library_clause),
LibraryClause {
span: code.token_span(),
name_list: vec![code.s1("foo").ident().into_ref()],
}
)
}
#[test]
fn test_library_clause_multiple_names() {
let code = Code::new("library foo, bar;");
assert_eq!(
code.with_stream_no_diagnostics(parse_library_clause),
LibraryClause {
span: code.token_span(),
name_list: vec![
code.s1("foo").ident().into_ref(),
code.s1("bar").ident().into_ref()
],
},
)
}
#[test]
fn test_use_clause_single_name() {
let code = Code::new("use lib.foo;");
assert_eq!(
code.with_stream_no_diagnostics(parse_use_clause),
WithTokenSpan::new(
UseClause {
span: code.token_span(),
name_list: vec![code.s1("lib.foo").name()],
},
code.token_span()
),
)
}
#[test]
fn test_use_clause_multiple_names() {
let code = Code::new("use foo.'a', lib.bar.all;");
assert_eq!(
code.with_stream_no_diagnostics(parse_use_clause),
WithTokenSpan::new(
UseClause {
span: code.token_span(),
name_list: vec![code.s1("foo.'a'").name(), code.s1("lib.bar.all").name()],
},
code.token_span()
),
)
}
#[test]
fn test_context_reference_single_name() {
let code = Code::new("context lib.foo;");
assert_eq!(
code.with_stream_no_diagnostics(parse_context),
DeclarationOrReference::Reference(ContextReference {
span: code.token_span(),
name_list: vec![code.s1("lib.foo").name()],
})
)
}
#[test]
fn test_context_clause() {
let variants = [
&"\
context ident is
end;
",
&"\
context ident is
end context;
",
&"\
context ident is
end ident;
",
&"\
context ident is
end context ident;
",
];
for (idx, variant) in variants.iter().enumerate() {
let has_end_ident = idx >= 2;
let code = Code::new(variant);
assert_eq!(
code.with_stream_no_diagnostics(parse_context),
DeclarationOrReference::Declaration(ContextDeclaration {
span: code.token_span(),
ident: code.s1("ident").decl_ident(),
items: vec![],
end_token: code.s1("end").token(),
end_ident_pos: if has_end_ident {
Some(code.s("ident", 2).token())
} else {
None
},
})
);
}
}
#[test]
fn test_context_clause_error_end_identifier_mismatch() {
let code = Code::new(
"\
context ident is
end context ident2;
",
);
let (context, diagnostics) = code.with_stream_diagnostics(parse_context);
assert_eq!(
diagnostics,
vec![Diagnostic::syntax_error(
code.s1("ident2"),
"End identifier mismatch, expected ident",
)]
);
assert_eq!(
context,
DeclarationOrReference::Declaration(ContextDeclaration {
span: code.token_span(),
ident: code.s1("ident").decl_ident(),
items: vec![],
end_token: code.s1("end").token(),
end_ident_pos: None,
})
);
}
#[test]
fn test_context_clause_items() {
let code = Code::new(
"\
context ident is
library foo;
use foo.bar;
context foo.ctx;
end context;
",
);
assert_eq!(
code.with_stream_no_diagnostics(parse_context),
DeclarationOrReference::Declaration(ContextDeclaration {
span: code.token_span(),
ident: code.s1("ident").decl_ident(),
items: vec![
ContextItem::Library(LibraryClause {
span: TokenSpan::new(code.s("library", 1).token(), code.s(";", 1).token()),
name_list: vec![code.s1("foo").ident().into_ref()],
}),
ContextItem::Use(UseClause {
span: code.s1("use foo.bar;").token_span(),
name_list: vec![code.s1("foo.bar").name()],
}),
ContextItem::Context(ContextReference {
span: TokenSpan::new(code.s("context", 2).token(), code.s(";", 3).token()),
name_list: vec![code.s1("foo.ctx").name()],
}),
],
end_token: code.s1("end").token(),
end_ident_pos: None,
})
)
}
#[test]
pub fn test_pos_of_context_elements() {
let code = Code::new(
"\
context my_context is
library ieee, env;
context my_context;
use ieee.std_logic_1164.all, std.env.xyz;
end my_context;
",
);
let ctx = code.tokenize();
let lib = code.context_declaration();
assert_eq!(
lib.items[0].get_pos(&ctx),
code.s1("library ieee, env;").pos()
);
assert_eq!(
lib.items[1].get_pos(&ctx),
code.s1("context my_context;").pos()
);
assert_eq!(
lib.items[2].get_pos(&ctx),
code.s1("use ieee.std_logic_1164.all, std.env.xyz;").pos()
);
}
#[test]
pub fn test_token_span() {
let code = Code::new(
"\
context my_context is
library ieee, env;
context my_context;
use ieee.std_logic_1164.all, std.env.xyz;
end my_context;
",
);
let ctx = code.tokenize();
let lib = code.context_declaration();
let lib_token_string: Vec<String> = lib.items[0]
.get_token_slice(&ctx)
.iter()
.map(token_to_string)
.collect();
let ctx_token_string: Vec<String> = lib.items[1]
.get_token_slice(&ctx)
.iter()
.map(token_to_string)
.collect();
let use_token_string: Vec<String> = lib.items[2]
.get_token_slice(&ctx)
.iter()
.map(token_to_string)
.collect();
assert_eq!(lib_token_string, vec!["library", "ieee", ",", "env", ";"],);
assert_eq!(ctx_token_string, vec!["context", "my_context", ";"],);
assert_eq!(
use_token_string,
vec![
"use",
"ieee",
".",
"std_logic_1164",
".",
"all",
",",
"std",
".",
"env",
".",
"xyz",
";"
],
);
}
}