diff --git a/vhdl_lang/src/completion.rs b/vhdl_lang/src/completion.rs index e6e8d4a0..2582899d 100644 --- a/vhdl_lang/src/completion.rs +++ b/vhdl_lang/src/completion.rs @@ -5,24 +5,24 @@ // Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com use crate::analysis::DesignRoot; -use crate::ast::search::{Found, FoundDeclaration, NotFinished, SearchState, Searcher}; -use crate::ast::{ - ArchitectureBody, ConcurrentStatement, Designator, MapAspect, ObjectClass, RangeAttribute, - SignalAttribute, TypeAttribute, -}; -use crate::data::{ContentReader, Symbol}; -use crate::named_entity::{self, AsUnique, DesignEnt, HasEntityId, NamedEntities, Region}; -use crate::syntax::Kind::*; -use crate::syntax::{Kind, Symbols, Token, TokenAccess, Tokenizer}; -use crate::{ - AnyEntKind, Design, EntRef, EntityId, HasTokenSpan, Object, Overloaded, Position, Source, -}; -use itertools::{chain, Itertools}; -use std::collections::HashSet; -use std::iter::once; -use vhdl_lang::ast::search::Finished; -use vhdl_lang::ast::AttributeDesignator; -use vhdl_lang::named_entity::Visibility; +use crate::ast::{AttributeDesignator, Designator}; +use crate::completion::attributes::completions_for_attribute_name; +use crate::completion::generic::generic_completions; +use crate::completion::libraries::list_all_libraries; +use crate::completion::map_aspect::completions_for_map_aspect; +use crate::completion::selected::completions_for_selected_name; +use crate::completion::tokenizer::tokenize_input; +use crate::syntax::Kind; +use crate::{EntRef, Position, Source}; + +mod attributes; +mod entity_instantiation; +mod generic; +mod libraries; +mod map_aspect; +mod region; +mod selected; +mod tokenizer; #[derive(Debug, PartialEq, Clone)] pub enum CompletionItem<'a> { @@ -62,560 +62,10 @@ pub enum CompletionItem<'a> { macro_rules! kind { ($kind: pat) => { - Token { kind: $kind, .. } + crate::syntax::Token { kind: $kind, .. } }; } -#[derive(Eq, PartialEq, Debug)] -enum MapAspectKind { - Port, - Generic, -} - -impl<'a> Region<'a> { - /// From this region, extracts those `AnyEntKind::Object`s where the class of the - /// object matches the specified class. - fn extract_objects_with_class(&self, object_class: ObjectClass) -> Vec { - self.entities - .values() - .filter_map(|ent| ent.as_unique()) - .filter_map(|ent| match &ent.kind { - AnyEntKind::Object(obj) if obj.class == object_class => Some(ent.id), - AnyEntKind::Overloaded(Overloaded::InterfaceSubprogram(_)) - if object_class == ObjectClass::Constant => - { - Some(ent.id) - } - AnyEntKind::Type(named_entity::Type::Interface) - if object_class == ObjectClass::Constant => - { - Some(ent.id) - } - _ => None, - }) - .collect() - } -} - -impl DesignRoot { - /// Extracts the name of ports or generics from an AST for an entity with a certain ID. - /// The entity can be an `Entity`, `Component` or `Package`. - /// - /// # Arguments - /// - /// * `object_class` - What to extract. `ObjectClass::Signal` extracts ports - /// while `ObjectClass::Constant` extracts constants. - fn extract_port_or_generic_names( - &self, - id: EntityId, - object_class: ObjectClass, - ) -> Vec { - let cmp_ent = self.get_ent(id); - match cmp_ent.kind() { - AnyEntKind::Component(region) => region.extract_objects_with_class(object_class), - AnyEntKind::Design(Design::Entity(_, region)) => { - region.extract_objects_with_class(object_class) - } - AnyEntKind::Design(Design::UninstPackage(_, region)) => { - region.extract_objects_with_class(object_class) - } - _ => vec![], - } - } - - pub fn extract_port_names(&self, id: EntityId) -> Vec { - self.extract_port_or_generic_names(id, ObjectClass::Signal) - } - - pub fn extract_generic_names(&self, id: EntityId) -> Vec { - self.extract_port_or_generic_names(id, ObjectClass::Constant) - } -} - -/// Searches completions for map aspects (VHDL port maps and generic maps). -/// Currently, this only means the formal part (i.e., the left hand side of a port or generic assignment) -/// but not the actual part. -struct MapAspectSearcher<'a> { - root: &'a DesignRoot, - cursor: Position, - completions: Vec>, -} - -impl<'a> MapAspectSearcher<'a> { - pub fn new(root: &'a DesignRoot, cursor: Position) -> MapAspectSearcher<'a> { - MapAspectSearcher { - root, - cursor, - completions: Vec::new(), - } - } - - /// Loads completion options for the given map aspect. - /// Returns `true`, when the cursor is inside the map aspect and the search should not continue. - /// Returns `false` otherwise - fn load_completions_for_map_aspect( - &mut self, - ent_ref: Option, - map: &MapAspect, - ctx: &dyn TokenAccess, - kind: MapAspectKind, - ) -> bool { - if !map.span(ctx).contains(self.cursor) { - return false; - } - let formals_in_map: HashSet = HashSet::from_iter(map.formals().flatten()); - if let Some(ent) = ent_ref { - let ids = match kind { - MapAspectKind::Port => self.root.extract_port_names(ent), - MapAspectKind::Generic => self.root.extract_generic_names(ent), - }; - self.completions.extend( - ids.iter() - .filter(|id| !formals_in_map.contains(id)) - .map(|id| CompletionItem::Formal(self.root.get_ent(*id))), - ); - } - true - } -} - -impl<'a> Searcher for MapAspectSearcher<'a> { - /// Visit an instantiation statement extracting completions for ports or generics. - fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration) -> SearchState { - match decl { - FoundDeclaration::ConcurrentStatement(stmt) => { - if let ConcurrentStatement::Instance(inst) = &stmt.statement.item { - if let Some(map) = &inst.generic_map { - if self.load_completions_for_map_aspect( - inst.entity_reference(), - map, - ctx, - MapAspectKind::Generic, - ) { - return Finished(Found); - } - } - if let Some(map) = &inst.port_map { - if self.load_completions_for_map_aspect( - inst.entity_reference(), - map, - ctx, - MapAspectKind::Port, - ) { - return Finished(Found); - } - } - } - } - FoundDeclaration::PackageInstance(inst) => { - if let Some(map) = &inst.generic_map { - if self.load_completions_for_map_aspect( - inst.package_name.item.get_suffix_reference(), - map, - ctx, - MapAspectKind::Generic, - ) { - return Finished(Found); - } - } - } - _ => {} - } - NotFinished - } -} - -/// Tokenizes `source` up to `cursor` but no further. The last token returned is the token -/// where the cursor currently resides or the token right before the cursor. -/// -/// Examples: -/// -/// input = "use ieee.std_logic_1|164.a" -/// ^ cursor position -/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} -/// -/// input = "use ieee.std_logic_1164|.a" -/// ^ cursor position -/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} -/// -/// input = "use ieee.std_logic_1164.|a" -/// ^ cursor position -/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT} -/// input = "use ieee.std_logic_1164.a|" -/// ^ cursor position -/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT, a} -/// -/// On error, or if the source is empty, returns an empty vector. -fn tokenize_input(symbols: &Symbols, source: &Source, cursor: Position) -> Vec { - let contents = source.contents(); - let mut tokenizer = Tokenizer::new(symbols, source, ContentReader::new(&contents)); - let mut tokens = Vec::new(); - loop { - match tokenizer.pop() { - Ok(Some(token)) => { - if token.pos.start() >= cursor { - break; - } - tokens.push(token); - } - Ok(None) => break, - Err(_) => return vec![], - } - } - tokens -} - -/// helper function to list the name of all available libraries -fn list_all_libraries(root: &DesignRoot) -> Vec { - root.libraries() - .map(|lib| CompletionItem::Simple(root.get_ent(lib.id()))) - .chain(once(CompletionItem::Work)) - .collect() -} - -/// This is the most general-purpose completion provider. -/// This provider publishes all visible symbols reachable from some context. -/// This will, among other things produce many "non-regular" symbols, such as -/// operator symbols or specific characters. If possible, -/// this searcher should therefore be avoided in favor of a more specific completion provider. -struct CompletionSearcher<'a> { - root: &'a DesignRoot, - cursor: Position, - completions: Vec>, -} - -impl<'a> CompletionSearcher<'a> { - pub fn new(cursor: Position, design_root: &'a DesignRoot) -> CompletionSearcher<'a> { - CompletionSearcher { - root: design_root, - cursor, - completions: Vec::new(), - } - } -} - -impl<'a> CompletionSearcher<'a> { - fn add_entity_instantiations(&mut self, ctx: &dyn TokenAccess, body: &ArchitectureBody) { - let Some(ent_id) = body.ident.decl.get() else { - return; - }; - let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { - return; - }; - // Early-exit for when we are inside a statement. - for statement in &body.statements { - let pos = &statement.statement.pos(ctx); - - // Early exit. The cursor is below the current statement. - if pos.start() > self.cursor { - break; - } - - if pos.contains(self.cursor) { - return; - } - } - self.completions.extend( - self.root - .get_visible_entities_from_entity(&ent) - .map(|eid| entity_to_completion_item(self.root, eid)), - ); - } -} - -impl<'a> Searcher for CompletionSearcher<'a> { - fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration) -> SearchState { - let ent_id = match decl { - FoundDeclaration::Entity(ent_decl) => { - if !ent_decl.get_pos(ctx).contains(self.cursor) { - return NotFinished; - } - ent_decl.ident.decl.get() - } - FoundDeclaration::Architecture(body) => { - if !body.get_pos(ctx).contains(self.cursor) { - return NotFinished; - } - self.add_entity_instantiations(ctx, body); - body.ident.decl.get() - } - FoundDeclaration::Package(package) => { - if !package.get_pos(ctx).contains(self.cursor) { - return NotFinished; - } - package.ident.decl.get() - } - FoundDeclaration::PackageBody(package) => { - if !package.get_pos(ctx).contains(self.cursor) { - return NotFinished; - } - package.ident.decl.get() - } - _ => return NotFinished, - }; - let Some(ent_id) = ent_id else { - return Finished(Found); - }; - let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { - return Finished(Found); - }; - self.completions.extend(visible_entities_from(ent.kind())); - Finished(Found) - } -} - -fn entity_to_completion_item(root: &DesignRoot, eid: EntityId) -> CompletionItem { - let ent = root.get_ent(eid); - match ent.kind() { - AnyEntKind::Design(Design::Entity(..)) => { - let architectures = get_architectures_for_entity(ent, root); - CompletionItem::EntityInstantiation(ent, architectures) - } - _ => CompletionItem::Simple(ent), - } -} - -/// Returns a vec populated with all architectures that belong to a given entity -fn get_architectures_for_entity<'a>(ent: EntRef<'a>, root: &'a DesignRoot) -> Vec> { - let Some(lib_symbol) = ent.library_name() else { - return vec![]; - }; - let Some(lib) = root.get_lib(lib_symbol) else { - return vec![]; - }; - let Some(sym) = ent.designator().as_identifier() else { - return vec![]; - }; - lib.secondary_units(sym) - .filter_map(|locked_unit| locked_unit.unit.get()) - .filter_map(|read_guard| read_guard.ent_id()) - .map(|eid| root.get_ent(eid)) - .collect() -} - -impl DesignRoot { - /// List all entities (entities in this context is a VHDL entity, not a `DesignEnt` or similar) - /// that are visible from another VHDL entity. - pub fn get_visible_entities_from_entity( - &self, - ent: &DesignEnt, - ) -> impl Iterator { - let mut entities: HashSet = HashSet::new(); - if let Design::Architecture(vis, _, ent_of_arch) = ent.kind() { - for ent_ref in vis.visible() { - match ent_ref.kind() { - AnyEntKind::Design(Design::Entity(..)) => { - entities.insert(ent_ref.id()); - } - AnyEntKind::Library => { - let Some(name) = ent_ref.library_name() else { - continue; - }; - let Some(lib) = self.get_lib(name) else { - continue; - }; - let itr = lib - .units() - .flat_map(|locked_unit| locked_unit.unit.get()) - .filter(|design_unit| design_unit.is_entity()) - .flat_map(|design_unit| design_unit.ent_id()) - .filter(|id| id != &ent_of_arch.id()); // Remove the entity that belongs to this architecture - entities.extend(itr); - } - _ => {} - } - } - } - entities.into_iter() - } -} - -fn visible_entities_from<'a>(design: &'a Design<'a>) -> Vec> { - use Design::*; - match design { - Entity(visibility, region) - | UninstPackage(visibility, region) - | Architecture(visibility, region, _) - | Package(visibility, region) - | PackageBody(visibility, region) => chain( - completion_items_from_region(region), - completion_items_from_visibility(visibility), - ) - .collect_vec(), - PackageInstance(region) | InterfacePackageInstance(region) | Context(region) => { - completion_items_from_region(region).collect_vec() - } - Configuration => vec![], - } -} - -fn named_entities_to_completion_item<'a>( - named_entities: &'a NamedEntities<'a>, -) -> CompletionItem<'a> { - match named_entities { - NamedEntities::Single(ent) => CompletionItem::Simple(ent), - NamedEntities::Overloaded(overloaded) => match overloaded.as_unique() { - None => CompletionItem::Overloaded(overloaded.designator().clone(), overloaded.len()), - Some(ent) => CompletionItem::Simple(ent), - }, - } -} - -fn completion_items_from_region<'a>( - region: &'a Region<'a>, -) -> impl Iterator> { - region - .entities - .values() - .map(named_entities_to_completion_item) -} - -fn completion_items_from_visibility<'a>( - visibility: &'a Visibility<'a>, -) -> impl Iterator> { - visibility - .visible() - .unique() - .map(CompletionItem::Simple) - .chain( - visibility - .all_in_region() - .flat_map(|visible_region| completion_items_from_region(visible_region.region())), - ) -} - -/// Returns completions applicable when calling `foo.` where `foo` is amn object of some type. -fn completions_for_type<'a>(typ: &'a named_entity::Type<'a>) -> Vec> { - use named_entity::Type::*; - match typ { - Record(record_region) => record_region - .iter() - .map(|item| CompletionItem::Simple(item.ent)) - .collect(), - Alias(type_ent) => completions_for_type(type_ent.kind()), - Access(subtype) => { - let mut completions = completions_for_type(subtype.type_mark().kind()); - completions.push(CompletionItem::Keyword(All)); - completions - } - Protected(region, _) => completion_items_from_region(region).collect_vec(), - _ => vec![], - } -} - -/// Returns completions applicable when calling `foo.` where `foo` is some design -/// (i.e. entity or package). -fn completions_for_design<'a>(design: &'a Design<'a>) -> Vec> { - use Design::*; - match design { - Package(_, region) | PackageInstance(region) | InterfacePackageInstance(region) => { - completion_items_from_region(region) - .chain(once(CompletionItem::Keyword(All))) - .collect() - } - _ => vec![], - } -} - -/// List the name of all primary units for a given library. -/// If the library is non-resolvable, list an empty vector -fn list_primaries_for_lib<'a>(root: &'a DesignRoot, lib: &Symbol) -> Vec> { - let Some(lib) = root.get_lib(lib) else { - return vec![]; - }; - lib.primary_units() - .filter_map(|it| it.unit.get().and_then(|unit| unit.ent_id())) - .map(|id| CompletionItem::Simple(root.get_ent(id))) - .collect() -} - -fn completions_after_dot<'b>(root: &'b DesignRoot, ent: EntRef<'b>) -> Vec> { - use AnyEntKind::*; - match ent.kind() { - Object(object) => completions_for_type(object.subtype.type_mark().kind()), - Design(design) => completions_for_design(design), - Library => ent - .library_name() - .map(|sym| list_primaries_for_lib(root, sym)) - .unwrap_or_default(), - _ => vec![], - } -} - -fn extend_attributes_of_objects(obj: &Object, attributes: &mut Vec) { - extend_attributes_of_type(obj.subtype.type_mark().kind(), attributes); - attributes.push(AttributeDesignator::Type(TypeAttribute::Subtype)); - if obj.class == ObjectClass::Signal { - use SignalAttribute::*; - attributes.extend( - [ - Delayed, - Stable, - Quiet, - Transaction, - Event, - Active, - LastEvent, - LastActive, - LastValue, - Driving, - DrivingValue, - ] - .map(AttributeDesignator::Signal), - ); - } - if obj.subtype.type_mark().kind().is_array() { - attributes.push(AttributeDesignator::Type(TypeAttribute::Element)); - } -} - -fn extend_attributes_of_type(typ: &named_entity::Type, attributes: &mut Vec) { - use AttributeDesignator::*; - if typ.is_scalar() { - attributes.extend([Left, Right, Low, High, Ascending, Image, Value]); - } else if typ.is_array() { - attributes.extend([ - Left, - Right, - Low, - High, - Range(RangeAttribute::Range), - Range(RangeAttribute::ReverseRange), - Length, - Ascending, - ]); - } - if typ.is_discrete() { - attributes.extend([Pos, Val, Succ, Pred, LeftOf, RightOf]); - } -} - -fn attributes_of_ent(ent: EntRef) -> Vec { - let mut attributes: Vec = Vec::new(); - attributes.extend([ - AttributeDesignator::SimpleName, - AttributeDesignator::InstanceName, - AttributeDesignator::PathName, - ]); - - match ent.kind() { - AnyEntKind::Type(typ) => extend_attributes_of_type(typ, &mut attributes), - AnyEntKind::Object(obj) => extend_attributes_of_objects(obj, &mut attributes), - AnyEntKind::View(_) => attributes.push(AttributeDesignator::Converse), - _ => {} - } - attributes - .into_iter() - .map(CompletionItem::Attribute) - .chain( - ent.attrs - .values() - .map(|(_, b)| b) - .map(|ent| CompletionItem::Simple(ent.ent)), - ) - .collect() -} - /// Main entry point for completion. Given a source-file and a cursor position, /// lists available completion options at the cursor position. pub fn list_completion_options<'a>( @@ -623,655 +73,33 @@ pub fn list_completion_options<'a>( source: &Source, cursor: Position, ) -> Vec> { + use crate::syntax::Kind::*; let tokens = tokenize_input(root.symbols(), source, cursor); match &tokens[..] { - [.., kind!(Library)] | [.., kind!(Use)] | [.., kind!(Use), kind!(Identifier)] => { - list_all_libraries(root) - } + [.., kind!(Library)] + | [.., kind!(Library), kind!(Identifier)] + | [.., kind!(Use)] + | [.., kind!(Use), kind!(Identifier)] => list_all_libraries(root), [.., token, kind!(Dot)] | [.., token, kind!(Dot), kind!(Identifier)] => { // get the entity before the token. // We rely on the syntax parsing to be resilient enough for this to yield a reasonable value. // Otherwise, we just return an empty value. if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) { - completions_after_dot(root, ent) + completions_for_selected_name(root, ent) } else { vec![] } } [.., token, kind!(Tick)] | [.., token, kind!(Tick), kind!(Identifier)] => { if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) { - attributes_of_ent(ent) + completions_for_attribute_name(ent) } else { vec![] } } [.., kind!(LeftPar | Comma)] | [.., kind!(LeftPar | Comma), kind!(Identifier)] => { - let mut searcher = MapAspectSearcher::new(root, cursor); - let _ = root.search_source(source, &mut searcher); - searcher.completions + completions_for_map_aspect(root, cursor, source) } - _ => { - let mut searcher = CompletionSearcher::new(cursor, root); - let _ = root.search_source(source, &mut searcher); - searcher.completions - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; - use crate::completion::tokenize_input; - use crate::syntax::test::{assert_eq_unordered, Code}; - use assert_matches::assert_matches; - use pretty_assertions::assert_eq; - - #[test] - fn tokenizing_an_empty_input() { - let input = Code::new(""); - let tokens = tokenize_input(&input.symbols, input.source(), Position::new(0, 0)); - assert_eq!(tokens.len(), 0); - } - - #[test] - fn tokenizing_stops_at_the_cursors_position() { - let input = Code::new("use ieee.std_logic_1164.all"); - let mut cursor = input.s1("std_logic_11").pos().end(); - let tokens = tokenize_input(&input.symbols, input.source(), cursor); - assert_matches!( - tokens[..], - [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] - ); - cursor = input.s1("std_logic_1164").pos().end(); - let tokens = tokenize_input(&input.symbols, input.source(), cursor); - assert_matches!( - tokens[..], - [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] - ); - cursor = input.s1("std_logic_1164.").pos().end(); - let tokens = tokenize_input(&input.symbols, input.source(), cursor); - assert_matches!( - tokens[..], - [ - kind!(Use), - kind!(Identifier), - kind!(Dot), - kind!(Identifier), - kind!(Dot) - ] - ); - cursor = input.s1("std_logic_1164.all").pos().end(); - let tokens = tokenize_input(&input.symbols, input.source(), cursor); - assert_matches!( - tokens[..], - [ - kind!(Use), - kind!(Identifier), - kind!(Dot), - kind!(Identifier), - kind!(Dot), - kind!(All) - ] - ); - } - - #[test] - pub fn completing_libraries() { - let input = LibraryBuilder::new(); - let code = Code::new("library "); - let (root, _) = input.get_analyzed_root(); - let cursor = code.s1("library ").pos().end(); - let options = list_completion_options(&root, code.source(), cursor); - assert_eq!(options, list_all_libraries(&root)) - } - - #[test] - pub fn completing_primaries() { - let mut builder = LibraryBuilder::new(); - let code = builder.code( - "libname", - "\ -use std. - --- Need this package so that the 'use std.' is associated and can be analyzed correctly -package x is -end package x; -", - ); - let (root, _) = builder.get_analyzed_root(); - let cursor = code.s1("use std.").end(); - let options = list_completion_options(&root, code.source(), cursor); - assert_eq_unordered( - &options, - &[ - CompletionItem::Simple(root.find_textio_pkg()), - CompletionItem::Simple(root.find_standard_pkg()), - CompletionItem::Simple(root.find_env_pkg()), - ], - ); - - let mut builder = LibraryBuilder::new(); - let code = builder.code( - "libname", - "\ -use std.t - --- Need this package so that the 'use std.' is associated and can be analyzed correctly -package x is -end package x; -", - ); - let (root, _) = builder.get_analyzed_root(); - let cursor = code.s1("use std.t").end(); - let options = list_completion_options(&root, code.source(), cursor); - // Note that the filtering only happens at client side - assert_eq_unordered( - &options, - &[ - CompletionItem::Simple(root.find_textio_pkg()), - CompletionItem::Simple(root.find_standard_pkg()), - CompletionItem::Simple(root.find_env_pkg()), - ], - ); - } - - #[test] - pub fn completing_declarations() { - let mut input = LibraryBuilder::new(); - let code = input.code( - "libname", - "\ -use std.env. - --- Need this package sp that the 'use std.' is associated and can be analyzed correctly -package x is -end package x; -", - ); - let (root, _) = input.get_analyzed_root(); - let cursor = code.s1("use std.env.").end(); - let options = list_completion_options(&root, code.source(), cursor); - - assert_eq_unordered( - &options, - &[ - CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("stop")), 2), - CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("finish")), 2), - CompletionItem::Simple(root.find_env_symbol("resolution_limit")), - CompletionItem::Keyword(All), - ], - ); - } - - #[test] - pub fn completing_instantiation_statement() { - let mut input = LibraryBuilder::new(); - let code = input.code( - "libname", - "\ - entity my_ent is - end entity my_ent; - - architecture arch of my_ent is - component comp is - generic ( - A: natural := 5; - B: integer - ); - port ( - clk : in bit; - rst : in bit; - dout : out bit - ); - end component comp; - signal clk, rst: bit; - begin - comp_inst: comp - generic map ( - A => 2 - ) - port map ( - clk => clk - ); - end arch; - ", - ); - let (root, _) = input.get_analyzed_root(); - let cursor = code.s1("generic map (").pos().end(); - let options = list_completion_options(&root, code.source(), cursor); - let ent = root - .search_reference(code.source(), code.s1("B").start()) - .unwrap(); - assert_eq!(options, vec![CompletionItem::Formal(ent)]); - - let rst = root - .search_reference(code.source(), code.s1("rst").start()) - .unwrap(); - - let dout = root - .search_reference(code.source(), code.s1("dout").start()) - .unwrap(); - - let clk_signal = root - .search_reference( - code.source(), - code.s1("signal clk, rst: bit;").s1("clk").start(), - ) - .unwrap(); - - let rst_signal = root - .search_reference( - code.source(), - code.s1("signal clk, rst: bit;").s1("rst").start(), - ) - .unwrap(); - - let cursor = code.s1("port map (").pos().end(); - let options = list_completion_options(&root, code.source(), cursor); - assert!(options.contains(&CompletionItem::Formal(rst))); - assert!(options.contains(&CompletionItem::Formal(dout))); - assert_eq!(options.len(), 2); - let cursor = code - .s1("port map ( - clk =>") - .pos() - .end(); - let options = list_completion_options(&root, code.source(), cursor); - assert!(options.contains(&CompletionItem::Simple(clk_signal))); - assert!(options.contains(&CompletionItem::Simple(rst_signal))); - - let cursor = code - .s1("port map ( - clk => c") - .pos() - .end(); - let options = list_completion_options(&root, code.source(), cursor); - assert!(options.contains(&CompletionItem::Simple(clk_signal))); - assert!(options.contains(&CompletionItem::Simple(rst_signal))); - } - - #[test] - pub fn complete_in_generic_map() { - let mut input = LibraryBuilder::new(); - let code = input.code( - "libname", - "\ - package my_pkg is - generic ( - function foo(x: Integer) return bit; - function bar(y: Integer) return boolean; - type T; - x: natural - ); - end my_pkg; - - use work.my_pkg.all ; - package my_pkg_inst is new work.my_pkg - generic map ( - foo => foo - );", - ); - let (root, _) = input.get_analyzed_root(); - let bar_func = root - .search_reference(code.source(), code.s1("bar").start()) - .unwrap(); - let x = root - .search_reference(code.source(), code.s1("x: natural").s1("x").start()) - .unwrap(); - let t = root - .search_reference(code.source(), code.s1("type T").s1("T").start()) - .unwrap(); - let cursor = code.s1("generic map (").pos().end(); - let options = list_completion_options(&root, code.source(), cursor); - assert!(options.contains(&CompletionItem::Formal(bar_func))); - assert!(options.contains(&CompletionItem::Formal(x))); - assert!(options.contains(&CompletionItem::Formal(t))); - assert_eq!(options.len(), 3); - } - - #[test] - fn complete_entities() { - let mut builder = LibraryBuilder::new(); - let code = builder.code( - "libname", - "\ -entity my_ent is -end my_ent; - -entity my_other_ent is -end my_other_ent; - -entity my_third_ent is -end my_third_ent; - -architecture arch of my_third_ent is -begin -end arch; - ", - ); - - let (root, _) = builder.get_analyzed_root(); - let cursor = code.s1("begin").end(); - let options = list_completion_options(&root, code.source(), cursor); - - let my_ent = root - .search_reference(code.source(), code.s1("my_ent").start()) - .unwrap(); - - let my_other_ent = root - .search_reference(code.source(), code.s1("my_other_ent").start()) - .unwrap(); - - assert!(options.contains(&CompletionItem::EntityInstantiation(my_ent, vec![]))); - assert!(options.contains(&CompletionItem::EntityInstantiation(my_other_ent, vec![]))); - } - - #[test] - fn complete_entities_from_different_libraries() { - let mut builder = LibraryBuilder::new(); - let code1 = builder.code( - "libA", - "\ -entity my_ent is -end my_ent; - ", - ); - - let code2 = builder.code( - "libB", - "\ -entity my_ent2 is -end my_ent2; - -entity my_ent3 is -end my_ent3; - -architecture arch of my_ent3 is -begin - -end arch; - - ", - ); - - let code3 = builder.code( - "libC", - "\ -entity my_ent2 is -end my_ent2; - -library libA; - -entity my_ent3 is -end my_ent3; - -architecture arch of my_ent3 is -begin - -end arch; - ", - ); - - let (root, diag) = builder.get_analyzed_root(); - check_no_diagnostics(&diag[..]); - let cursor = code2.s1("begin").end(); - let options = list_completion_options(&root, code2.source(), cursor); - - let my_ent2 = root - .search_reference(code2.source(), code2.s1("my_ent2").start()) - .unwrap(); - - assert!(options.contains(&CompletionItem::EntityInstantiation(my_ent2, vec![]))); - - let ent1 = root - .search_reference(code1.source(), code1.s1("my_ent").start()) - .unwrap(); - - let cursor = code3.s1("begin").end(); - let options = list_completion_options(&root, code3.source(), cursor); - - let my_ent2 = root - .search_reference(code3.source(), code3.s1("my_ent2").start()) - .unwrap(); - - assert!(options.contains(&CompletionItem::EntityInstantiation(my_ent2, vec![]))); - assert!(options.contains(&CompletionItem::EntityInstantiation(ent1, vec![]))); - } - - #[test] - pub fn entity_with_two_architecture() { - let mut builder = LibraryBuilder::new(); - let code1 = builder.code( - "libA", - "\ -entity my_ent is -end my_ent; - -architecture arch1 of my_ent is -begin -end arch1; - -architecture arch2 of my_ent is -begin -end arch2; - ", - ); - let code2 = builder.code( - "libA", - "\ -entity my_ent2 is -end my_ent2; - -architecture arch of my_ent2 is -begin - -end arch; - ", - ); - - let (root, diag) = builder.get_analyzed_root(); - check_no_diagnostics(&diag[..]); - let cursor = code2.s("begin", 1).end(); - let options = list_completion_options(&root, code2.source(), cursor); - - let ent = root - .search_reference(code1.source(), code1.s1("my_ent").start()) - .unwrap(); - - let arch1 = root - .search_reference(code1.source(), code1.s1("arch1").start()) - .unwrap(); - - let arch2 = root - .search_reference(code1.source(), code1.s1("arch2").start()) - .unwrap(); - - let applicable_options = options - .into_iter() - .filter_map(|option| match option { - CompletionItem::EntityInstantiation(ent, architectures) => { - Some((ent, architectures)) - } - _ => None, - }) - .collect_vec(); - println!("{:?}", applicable_options); - match &applicable_options[..] { - [(got_ent, architectures)] => { - assert_eq!(*got_ent, ent); - assert_eq_unordered(architectures, &[arch1, arch2]); - } - _ => panic!("Expected entity instantiation"), - } - } - - #[test] - pub fn completes_signals_and_ports() { - let mut builder = LibraryBuilder::new(); - let code = builder.code( - "libA", - "\ -entity my_ent is - port ( - foo : in bit - ); -end my_ent; - -architecture arch of my_ent is - signal bar : natural; - type foobaz is array(natural range <>) of bit; -begin -end arch; - ", - ); - - let (root, diag) = builder.get_analyzed_root(); - check_no_diagnostics(&diag); - let cursor = code.s1("begin").end(); - let options = list_completion_options(&root, code.source(), cursor); - - let ent1 = root - .search_reference(code.source(), code.s1("foo").start()) - .unwrap(); - - let ent2 = root - .search_reference(code.source(), code.s1("bar").start()) - .unwrap(); - - assert!(options.contains(&CompletionItem::Simple(ent1))); - assert!(options.contains(&CompletionItem::Simple(ent2))); - } - - #[test] - pub fn completes_selected_names() { - let mut builder = LibraryBuilder::new(); - let code = builder.code( - "libA", - "\ -entity my_ent is -end my_ent; - -architecture arch of my_ent is - type my_record is record - abc: bit; - def: bit; - end record; - - constant y: my_record := ('1', '1'); - constant z: bit := y. -begin -end arch; - ", - ); - - let (root, _) = builder.get_analyzed_root(); - let cursor = code.s1("y.").end(); - let options = list_completion_options(&root, code.source(), cursor); - - let ent1 = root - .search_reference(code.source(), code.s1("abc").start()) - .unwrap(); - - let ent2 = root - .search_reference(code.source(), code.s1("def").start()) - .unwrap(); - - assert_eq_unordered( - &options, - &[CompletionItem::Simple(ent1), CompletionItem::Simple(ent2)], - ) - } - - #[test] - pub fn completes_attributes() { - use AttributeDesignator::*; - use TypeAttribute::*; - - let mut builder = LibraryBuilder::new(); - let code = builder.code( - "libA", - "\ -package my_pkg is - constant foo : BIT_VECTOR := \"001\"; - constant bar: NATURAL := foo' -end package; -", - ); - - let (root, _) = builder.get_analyzed_root(); - let cursor = code.s1("foo'").end(); - let options = list_completion_options(&root, code.source(), cursor); - - let expected_options = [ - Type(Element), - Type(Subtype), - Range(RangeAttribute::Range), - Range(RangeAttribute::ReverseRange), - Ascending, - Left, - Right, - High, - Low, - Length, - InstanceName, - SimpleName, - PathName, - ] - .map(CompletionItem::Attribute); - - assert_eq_unordered(&options, &expected_options); - } - - #[test] - pub fn completes_signal_attributes() { - use AttributeDesignator::*; - use SignalAttribute::*; - use TypeAttribute::*; - - let mut builder = LibraryBuilder::new(); - let code = builder.code( - "libA", - "\ -package my_pkg is - signal foo : BIT_VECTOR := \"001\"; - signal bar: NATURAL := foo' -end package; -", - ); - - let (root, _) = builder.get_analyzed_root(); - let cursor = code.s1("foo'").end(); - let options = list_completion_options(&root, code.source(), cursor); - - let expected_options = [ - Type(Element), - Type(Subtype), - Range(RangeAttribute::Range), - Range(RangeAttribute::ReverseRange), - Signal(Delayed), - Signal(Stable), - Signal(Quiet), - Signal(Transaction), - Signal(Event), - Signal(Active), - Signal(LastEvent), - Signal(LastActive), - Signal(LastValue), - Signal(Driving), - Signal(DrivingValue), - Ascending, - Left, - Right, - High, - Low, - Length, - InstanceName, - SimpleName, - PathName, - ] - .map(CompletionItem::Attribute); - - assert_eq_unordered(&options, &expected_options); + _ => generic_completions(root, cursor, source), } } diff --git a/vhdl_lang/src/completion/attributes.rs b/vhdl_lang/src/completion/attributes.rs new file mode 100644 index 00000000..620cd4c6 --- /dev/null +++ b/vhdl_lang/src/completion/attributes.rs @@ -0,0 +1,188 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::ast::{AttributeDesignator, ObjectClass, RangeAttribute, TypeAttribute}; +use crate::{named_entity, AnyEntKind, CompletionItem, EntRef, Object}; + +/// Produces completions for an attribute name, i.e., +/// `foo'` +/// The provided ent is the entity directly before the tick, i.e., +/// `foo` in the example above. +pub(crate) fn completions_for_attribute_name(ent: EntRef) -> Vec { + let mut attributes: Vec = Vec::new(); + attributes.extend([ + AttributeDesignator::SimpleName, + AttributeDesignator::InstanceName, + AttributeDesignator::PathName, + ]); + + match ent.kind() { + AnyEntKind::Type(typ) => extend_attributes_of_type(typ, &mut attributes), + AnyEntKind::Object(obj) => extend_attributes_of_objects(obj, &mut attributes), + AnyEntKind::View(_) => attributes.push(AttributeDesignator::Converse), + _ => {} + } + attributes + .into_iter() + .map(CompletionItem::Attribute) + .chain( + ent.attrs + .values() + .map(|(_, b)| b) + .map(|ent| CompletionItem::Simple(ent.ent)), + ) + .collect() +} + +/// Extends applicable attributes when the attribute name is a type. +fn extend_attributes_of_type(typ: &named_entity::Type, attributes: &mut Vec) { + use AttributeDesignator::*; + if typ.is_scalar() { + attributes.extend([Left, Right, Low, High, Ascending, Image, Value]); + } else if typ.is_array() { + attributes.extend([ + Left, + Right, + Low, + High, + Range(RangeAttribute::Range), + Range(RangeAttribute::ReverseRange), + Length, + Ascending, + ]); + } + if typ.is_discrete() { + attributes.extend([Pos, Val, Succ, Pred, LeftOf, RightOf]); + } +} + +/// Extends applicable attributes when the attribute name is an object. +fn extend_attributes_of_objects(obj: &Object, attributes: &mut Vec) { + extend_attributes_of_type(obj.subtype.type_mark().kind(), attributes); + attributes.push(AttributeDesignator::Type(TypeAttribute::Subtype)); + if obj.class == ObjectClass::Signal { + use crate::ast::SignalAttribute::*; + attributes.extend( + [ + Delayed, + Stable, + Quiet, + Transaction, + Event, + Active, + LastEvent, + LastActive, + LastValue, + Driving, + DrivingValue, + ] + .map(AttributeDesignator::Signal), + ); + } + if obj.subtype.type_mark().kind().is_array() { + attributes.push(AttributeDesignator::Type(TypeAttribute::Element)); + } +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::LibraryBuilder; + use crate::ast::RangeAttribute; + use crate::list_completion_options; + use crate::syntax::test::assert_eq_unordered; + use crate::CompletionItem; + + #[test] + pub fn completes_attributes() { + use crate::ast::AttributeDesignator::*; + use crate::ast::TypeAttribute::*; + + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +package my_pkg is + constant foo : BIT_VECTOR := \"001\"; + constant bar: NATURAL := foo' +end package; +", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("foo'").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let expected_options = [ + Type(Element), + Type(Subtype), + Range(RangeAttribute::Range), + Range(RangeAttribute::ReverseRange), + Ascending, + Left, + Right, + High, + Low, + Length, + InstanceName, + SimpleName, + PathName, + ] + .map(CompletionItem::Attribute); + + assert_eq_unordered(&options, &expected_options); + } + + #[test] + pub fn completes_signal_attributes() { + use crate::ast::AttributeDesignator::*; + use crate::ast::SignalAttribute::*; + use crate::ast::TypeAttribute::*; + + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +package my_pkg is + signal foo : BIT_VECTOR := \"001\"; + signal bar: NATURAL := foo' +end package; +", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("foo'").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let expected_options = [ + Type(Element), + Type(Subtype), + Range(RangeAttribute::Range), + Range(RangeAttribute::ReverseRange), + Signal(Delayed), + Signal(Stable), + Signal(Quiet), + Signal(Transaction), + Signal(Event), + Signal(Active), + Signal(LastEvent), + Signal(LastActive), + Signal(LastValue), + Signal(Driving), + Signal(DrivingValue), + Ascending, + Left, + Right, + High, + Low, + Length, + InstanceName, + SimpleName, + PathName, + ] + .map(CompletionItem::Attribute); + + assert_eq_unordered(&options, &expected_options); + } +} diff --git a/vhdl_lang/src/completion/entity_instantiation.rs b/vhdl_lang/src/completion/entity_instantiation.rs new file mode 100644 index 00000000..b3e60545 --- /dev/null +++ b/vhdl_lang/src/completion/entity_instantiation.rs @@ -0,0 +1,343 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::named_entity::DesignEnt; +use crate::{AnyEntKind, CompletionItem, Design, EntRef, EntityId, HasEntityId}; +use itertools::Itertools; +use std::collections::HashSet; + +/// List all entities (entities in this context is a VHDL entity, not a `DesignEnt` or similar) +/// that are visible from another VHDL entity. +pub(crate) fn get_visible_entities_from_entity<'a>( + root: &'a DesignRoot, + ent: &DesignEnt<'a>, +) -> Vec> { + let mut entities: HashSet = HashSet::new(); + if let Design::Architecture(vis, _, ent_of_arch) = ent.kind() { + for ent_ref in vis.visible() { + match ent_ref.kind() { + AnyEntKind::Design(Design::Entity(..)) => { + entities.insert(ent_ref.id()); + } + AnyEntKind::Library => { + let Some(name) = ent_ref.library_name() else { + continue; + }; + let Some(lib) = root.get_lib(name) else { + continue; + }; + let itr = lib + .units() + .flat_map(|locked_unit| locked_unit.unit.get()) + .filter(|design_unit| design_unit.is_entity()) + .flat_map(|design_unit| design_unit.ent_id()) + .filter(|id| id != &ent_of_arch.id()); // Remove the entity that belongs to this architecture + entities.extend(itr); + } + _ => {} + } + } + } + entities + .into_iter() + .map(|eid| root.get_ent(eid)) + .map(|ent| match ent.kind() { + AnyEntKind::Design(Design::Entity(..)) => { + let architectures = get_architectures_for_entity(ent, root); + CompletionItem::EntityInstantiation(ent, architectures) + } + _ => CompletionItem::Simple(ent), + }) + .collect_vec() +} + +/// Returns a vec populated with all architectures that belong to a given entity +fn get_architectures_for_entity<'a>(ent: EntRef<'a>, root: &'a DesignRoot) -> Vec> { + let Some(lib_symbol) = ent.library_name() else { + return vec![]; + }; + let Some(lib) = root.get_lib(lib_symbol) else { + return vec![]; + }; + let Some(sym) = ent.designator().as_identifier() else { + return vec![]; + }; + lib.secondary_units(sym) + .filter_map(|locked_unit| locked_unit.unit.get()) + .filter_map(|read_guard| read_guard.ent_id()) + .map(|eid| root.get_ent(eid)) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{assert_eq_unordered, check_no_diagnostics, LibraryBuilder}; + use crate::{list_completion_options, CompletionItem}; + use itertools::Itertools; + + #[test] + pub fn completing_instantiation_statement() { + let mut input = LibraryBuilder::new(); + let code = input.code( + "libname", + "\ + entity my_ent is + end entity my_ent; + + architecture arch of my_ent is + component comp is + generic ( + A: natural := 5; + B: integer + ); + port ( + clk : in bit; + rst : in bit; + dout : out bit + ); + end component comp; + signal clk, rst: bit; + begin + comp_inst: comp + generic map ( + A => 2 + ) + port map ( + clk => clk + ); + end arch; + ", + ); + let (root, _) = input.get_analyzed_root(); + let cursor = code.s1("generic map (").pos().end(); + let options = list_completion_options(&root, code.source(), cursor); + let ent = root + .search_reference(code.source(), code.s1("B").start()) + .unwrap(); + assert_eq!(options, vec![CompletionItem::Formal(ent)]); + + let rst = root + .search_reference(code.source(), code.s1("rst").start()) + .unwrap(); + + let dout = root + .search_reference(code.source(), code.s1("dout").start()) + .unwrap(); + + let clk_signal = root + .search_reference( + code.source(), + code.s1("signal clk, rst: bit;").s1("clk").start(), + ) + .unwrap(); + + let rst_signal = root + .search_reference( + code.source(), + code.s1("signal clk, rst: bit;").s1("rst").start(), + ) + .unwrap(); + + let cursor = code.s1("port map (").pos().end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Formal(rst))); + assert!(options.contains(&CompletionItem::Formal(dout))); + assert_eq!(options.len(), 2); + let cursor = code + .s1("port map ( + clk =>") + .pos() + .end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Simple(clk_signal))); + assert!(options.contains(&CompletionItem::Simple(rst_signal))); + + let cursor = code + .s1("port map ( + clk => c") + .pos() + .end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Simple(clk_signal))); + assert!(options.contains(&CompletionItem::Simple(rst_signal))); + } + + #[test] + fn complete_entities() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +entity my_ent is +end my_ent; + +entity my_other_ent is +end my_other_ent; + +entity my_third_ent is +end my_third_ent; + +architecture arch of my_third_ent is +begin +end arch; + ", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("begin").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let my_ent = root + .search_reference(code.source(), code.s1("my_ent").start()) + .unwrap(); + + let my_other_ent = root + .search_reference(code.source(), code.s1("my_other_ent").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::EntityInstantiation(my_ent, vec![]))); + assert!(options.contains(&CompletionItem::EntityInstantiation(my_other_ent, vec![]))); + } + + #[test] + fn complete_entities_from_different_libraries() { + let mut builder = LibraryBuilder::new(); + let code1 = builder.code( + "libA", + "\ +entity my_ent is +end my_ent; + ", + ); + + let code2 = builder.code( + "libB", + "\ +entity my_ent2 is +end my_ent2; + +entity my_ent3 is +end my_ent3; + +architecture arch of my_ent3 is +begin + +end arch; + + ", + ); + + let code3 = builder.code( + "libC", + "\ +entity my_ent2 is +end my_ent2; + +library libA; + +entity my_ent3 is +end my_ent3; + +architecture arch of my_ent3 is +begin + +end arch; + ", + ); + + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag[..]); + let cursor = code2.s1("begin").end(); + let options = list_completion_options(&root, code2.source(), cursor); + + let my_ent2 = root + .search_reference(code2.source(), code2.s1("my_ent2").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::EntityInstantiation(my_ent2, vec![]))); + + let ent1 = root + .search_reference(code1.source(), code1.s1("my_ent").start()) + .unwrap(); + + let cursor = code3.s1("begin").end(); + let options = list_completion_options(&root, code3.source(), cursor); + + let my_ent2 = root + .search_reference(code3.source(), code3.s1("my_ent2").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::EntityInstantiation(my_ent2, vec![]))); + assert!(options.contains(&CompletionItem::EntityInstantiation(ent1, vec![]))); + } + + #[test] + pub fn entity_with_two_architecture() { + let mut builder = LibraryBuilder::new(); + let code1 = builder.code( + "libA", + "\ +entity my_ent is +end my_ent; + +architecture arch1 of my_ent is +begin +end arch1; + +architecture arch2 of my_ent is +begin +end arch2; + ", + ); + let code2 = builder.code( + "libA", + "\ +entity my_ent2 is +end my_ent2; + +architecture arch of my_ent2 is +begin + +end arch; + ", + ); + + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag[..]); + let cursor = code2.s("begin", 1).end(); + let options = list_completion_options(&root, code2.source(), cursor); + + let ent = root + .search_reference(code1.source(), code1.s1("my_ent").start()) + .unwrap(); + + let arch1 = root + .search_reference(code1.source(), code1.s1("arch1").start()) + .unwrap(); + + let arch2 = root + .search_reference(code1.source(), code1.s1("arch2").start()) + .unwrap(); + + let applicable_options = options + .into_iter() + .filter_map(|option| match option { + CompletionItem::EntityInstantiation(ent, architectures) => { + Some((ent, architectures)) + } + _ => None, + }) + .collect_vec(); + println!("{:?}", applicable_options); + match &applicable_options[..] { + [(got_ent, architectures)] => { + pretty_assertions::assert_eq!(*got_ent, ent); + assert_eq_unordered(architectures, &[arch1, arch2]); + } + _ => panic!("Expected entity instantiation"), + } + } +} diff --git a/vhdl_lang/src/completion/generic.rs b/vhdl_lang/src/completion/generic.rs new file mode 100644 index 00000000..3ed3de21 --- /dev/null +++ b/vhdl_lang/src/completion/generic.rs @@ -0,0 +1,144 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::ast::search::{Finished, Found, FoundDeclaration, NotFinished, SearchState, Searcher}; +use crate::ast::ArchitectureBody; +use crate::completion::entity_instantiation::get_visible_entities_from_entity; +use crate::completion::region::completion_items_from_region; +use crate::named_entity::{DesignEnt, Visibility}; +use crate::{CompletionItem, Design, HasTokenSpan, Position, Source, TokenAccess}; +use itertools::{chain, Itertools}; +use vhdl_lang::analysis::DesignRoot; + +pub(crate) fn generic_completions<'a>( + root: &'a DesignRoot, + cursor: Position, + source: &Source, +) -> Vec> { + let mut searcher = CompletionSearcher::new(cursor, root); + let _ = root.search_source(source, &mut searcher); + searcher.completions +} + +/// This is the most general-purpose completion provider. +/// This provider publishes all visible symbols reachable from some context. +/// This will, among other things produce many "non-regular" symbols, such as +/// operator symbols or specific characters. If possible, +/// this searcher should therefore be avoided in favor of a more specific completion provider. +struct CompletionSearcher<'a> { + root: &'a DesignRoot, + cursor: Position, + completions: Vec>, +} + +impl<'a> CompletionSearcher<'a> { + pub fn new(cursor: Position, design_root: &'a DesignRoot) -> CompletionSearcher<'a> { + CompletionSearcher { + root: design_root, + cursor, + completions: Vec::new(), + } + } +} + +impl<'a> CompletionSearcher<'a> { + fn add_entity_instantiations(&mut self, ctx: &dyn TokenAccess, body: &ArchitectureBody) { + let Some(ent_id) = body.ident.decl.get() else { + return; + }; + let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { + return; + }; + // Early-exit for when we are inside a statement. + for statement in &body.statements { + let pos = &statement.statement.pos(ctx); + + // Early exit. The cursor is below the current statement. + if pos.start() > self.cursor { + break; + } + + if pos.contains(self.cursor) { + return; + } + } + self.completions + .extend(get_visible_entities_from_entity(self.root, &ent)); + } +} + +impl<'a> Searcher for CompletionSearcher<'a> { + fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration) -> SearchState { + let ent_id = match decl { + FoundDeclaration::Entity(ent_decl) => { + if !ent_decl.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + ent_decl.ident.decl.get() + } + FoundDeclaration::Architecture(body) => { + if !body.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + self.add_entity_instantiations(ctx, body); + body.ident.decl.get() + } + FoundDeclaration::Package(package) => { + if !package.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + package.ident.decl.get() + } + FoundDeclaration::PackageBody(package) => { + if !package.get_pos(ctx).contains(self.cursor) { + return NotFinished; + } + package.ident.decl.get() + } + _ => return NotFinished, + }; + let Some(ent_id) = ent_id else { + return Finished(Found); + }; + let Some(ent) = DesignEnt::from_any(self.root.get_ent(ent_id)) else { + return Finished(Found); + }; + self.completions.extend(visible_entities_from(ent.kind())); + Finished(Found) + } +} + +fn visible_entities_from<'a>(design: &'a Design<'a>) -> Vec> { + use Design::*; + match design { + Entity(visibility, region) + | UninstPackage(visibility, region) + | Architecture(visibility, region, _) + | Package(visibility, region) + | PackageBody(visibility, region) => chain( + completion_items_from_region(region), + completion_items_from_visibility(visibility), + ) + .collect_vec(), + PackageInstance(region) | InterfacePackageInstance(region) | Context(region) => { + completion_items_from_region(region).collect_vec() + } + Configuration => vec![], + } +} + +fn completion_items_from_visibility<'a>( + visibility: &'a Visibility<'a>, +) -> impl Iterator> { + visibility + .visible() + .unique() + .map(CompletionItem::Simple) + .chain( + visibility + .all_in_region() + .flat_map(|visible_region| completion_items_from_region(visible_region.region())), + ) +} diff --git a/vhdl_lang/src/completion/libraries.rs b/vhdl_lang/src/completion/libraries.rs new file mode 100644 index 00000000..4788cf2d --- /dev/null +++ b/vhdl_lang/src/completion/libraries.rs @@ -0,0 +1,33 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::CompletionItem; +use std::iter::once; + +/// Produces all available libraries. +pub(crate) fn list_all_libraries(root: &DesignRoot) -> Vec { + root.libraries() + .map(|lib| CompletionItem::Simple(root.get_ent(lib.id()))) + .chain(once(CompletionItem::Work)) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{Code, LibraryBuilder}; + use crate::completion::libraries::list_all_libraries; + use crate::list_completion_options; + + #[test] + pub fn completing_libraries() { + let input = LibraryBuilder::new(); + let code = Code::new("library "); + let (root, _) = input.get_analyzed_root(); + let cursor = code.end(); + let options = list_completion_options(&root, code.source(), cursor); + assert_eq!(options, list_all_libraries(&root)) + } +} diff --git a/vhdl_lang/src/completion/map_aspect.rs b/vhdl_lang/src/completion/map_aspect.rs new file mode 100644 index 00000000..a6be6e35 --- /dev/null +++ b/vhdl_lang/src/completion/map_aspect.rs @@ -0,0 +1,264 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::ast::search::{Finished, Found, FoundDeclaration, NotFinished, SearchState, Searcher}; +use crate::ast::{ConcurrentStatement, MapAspect, ObjectClass}; +use crate::named_entity::{AsUnique, Region}; +use crate::{ + named_entity, AnyEntKind, CompletionItem, Design, EntityId, Overloaded, Position, Source, + TokenAccess, +}; +use std::collections::HashSet; + +/// Produces completions for the left hand side of a map aspect, i.e., +/// `port map (` +pub(crate) fn completions_for_map_aspect<'a>( + root: &'a DesignRoot, + cursor: Position, + source: &Source, +) -> Vec> { + let mut searcher = MapAspectSearcher::new(root, cursor); + let _ = root.search_source(source, &mut searcher); + searcher.completions +} + +/// Searches completions for map aspects (VHDL port maps and generic maps). +/// Currently, this only means the formal part (i.e., the left hand side of a port or generic assignment) +/// but not the actual part. +struct MapAspectSearcher<'a> { + root: &'a DesignRoot, + cursor: Position, + completions: Vec>, +} + +impl<'a> MapAspectSearcher<'a> { + pub fn new(root: &'a DesignRoot, cursor: Position) -> MapAspectSearcher<'a> { + MapAspectSearcher { + root, + cursor, + completions: Vec::new(), + } + } + + /// Loads completion options for the given map aspect. + /// Returns `true`, when the cursor is inside the map aspect and the search should not continue. + /// Returns `false` otherwise + fn load_completions_for_map_aspect( + &mut self, + ent_ref: Option, + map: &MapAspect, + ctx: &dyn TokenAccess, + kind: MapAspectKind, + ) -> bool { + if !map.span(ctx).contains(self.cursor) { + return false; + } + let formals_in_map: HashSet = HashSet::from_iter(map.formals().flatten()); + if let Some(ent) = ent_ref { + let ids = match kind { + MapAspectKind::Port => extract_port_names(self.root, ent), + MapAspectKind::Generic => extract_generic_names(self.root, ent), + }; + self.completions.extend( + ids.into_iter() + .filter(|id| !formals_in_map.contains(id)) + .map(|id| CompletionItem::Formal(self.root.get_ent(id))), + ); + } + true + } +} + +impl<'a> Searcher for MapAspectSearcher<'a> { + /// Visit an instantiation statement extracting completions for ports or generics. + fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration) -> SearchState { + match decl { + FoundDeclaration::ConcurrentStatement(stmt) => { + if let ConcurrentStatement::Instance(inst) = &stmt.statement.item { + if let Some(map) = &inst.generic_map { + if self.load_completions_for_map_aspect( + inst.entity_reference(), + map, + ctx, + MapAspectKind::Generic, + ) { + return Finished(Found); + } + } + if let Some(map) = &inst.port_map { + if self.load_completions_for_map_aspect( + inst.entity_reference(), + map, + ctx, + MapAspectKind::Port, + ) { + return Finished(Found); + } + } + } + } + FoundDeclaration::PackageInstance(inst) => { + if let Some(map) = &inst.generic_map { + if self.load_completions_for_map_aspect( + inst.package_name.item.get_suffix_reference(), + map, + ctx, + MapAspectKind::Generic, + ) { + return Finished(Found); + } + } + } + _ => {} + } + NotFinished + } +} + +#[derive(Eq, PartialEq, Debug)] +enum MapAspectKind { + Port, + Generic, +} + +/// From this region, extracts those `AnyEntKind::Object`s where the class of the +/// object matches the specified class. +fn extract_objects_with_class(region: &Region, object_class: ObjectClass) -> Vec { + region + .entities + .values() + .filter_map(|ent| ent.as_unique()) + .filter_map(|ent| match &ent.kind { + AnyEntKind::Object(obj) if obj.class == object_class => Some(ent.id), + AnyEntKind::Overloaded(Overloaded::InterfaceSubprogram(_)) + if object_class == ObjectClass::Constant => + { + Some(ent.id) + } + AnyEntKind::Type(named_entity::Type::Interface) + if object_class == ObjectClass::Constant => + { + Some(ent.id) + } + _ => None, + }) + .collect() +} + +/// Extracts the name of ports or generics from an AST for an entity with a certain ID. +/// The entity can be an `Entity`, `Component` or `Package`. +/// +/// # Arguments +/// +/// * `object_class` - What to extract. `ObjectClass::Signal` extracts ports +/// while `ObjectClass::Constant` extracts constants. +fn extract_port_or_generic_names( + root: &DesignRoot, + id: EntityId, + object_class: ObjectClass, +) -> Vec { + let cmp_ent = root.get_ent(id); + match cmp_ent.kind() { + AnyEntKind::Component(region) => extract_objects_with_class(region, object_class), + AnyEntKind::Design(Design::Entity(_, region)) => { + extract_objects_with_class(region, object_class) + } + AnyEntKind::Design(Design::UninstPackage(_, region)) => { + extract_objects_with_class(region, object_class) + } + _ => vec![], + } +} + +pub fn extract_port_names(root: &DesignRoot, id: EntityId) -> Vec { + extract_port_or_generic_names(root, id, ObjectClass::Signal) +} + +pub fn extract_generic_names(root: &DesignRoot, id: EntityId) -> Vec { + extract_port_or_generic_names(root, id, ObjectClass::Constant) +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{check_no_diagnostics, LibraryBuilder}; + use crate::{list_completion_options, CompletionItem}; + + #[test] + pub fn complete_in_generic_map() { + let mut input = LibraryBuilder::new(); + let code = input.code( + "libname", + "\ + package my_pkg is + generic ( + function foo(x: Integer) return bit; + function bar(y: Integer) return boolean; + type T; + x: natural + ); + end my_pkg; + + use work.my_pkg.all ; + package my_pkg_inst is new work.my_pkg + generic map ( + foo => foo + );", + ); + let (root, _) = input.get_analyzed_root(); + let bar_func = root + .search_reference(code.source(), code.s1("bar").start()) + .unwrap(); + let x = root + .search_reference(code.source(), code.s1("x: natural").s1("x").start()) + .unwrap(); + let t = root + .search_reference(code.source(), code.s1("type T").s1("T").start()) + .unwrap(); + let cursor = code.s1("generic map (").pos().end(); + let options = list_completion_options(&root, code.source(), cursor); + assert!(options.contains(&CompletionItem::Formal(bar_func))); + assert!(options.contains(&CompletionItem::Formal(x))); + assert!(options.contains(&CompletionItem::Formal(t))); + pretty_assertions::assert_eq!(options.len(), 3); + } + + #[test] + pub fn completes_signals_and_ports() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +entity my_ent is + port ( + foo : in bit + ); +end my_ent; + +architecture arch of my_ent is + signal bar : natural; + type foobaz is array(natural range <>) of bit; +begin +end arch; + ", + ); + + let (root, diag) = builder.get_analyzed_root(); + check_no_diagnostics(&diag); + let cursor = code.s1("begin").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let ent1 = root + .search_reference(code.source(), code.s1("foo").start()) + .unwrap(); + + let ent2 = root + .search_reference(code.source(), code.s1("bar").start()) + .unwrap(); + + assert!(options.contains(&CompletionItem::Simple(ent1))); + assert!(options.contains(&CompletionItem::Simple(ent2))); + } +} diff --git a/vhdl_lang/src/completion/region.rs b/vhdl_lang/src/completion/region.rs new file mode 100644 index 00000000..c2627475 --- /dev/null +++ b/vhdl_lang/src/completion/region.rs @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::named_entity::{AsUnique, NamedEntities, Region}; +use crate::CompletionItem; + +pub(crate) fn completion_items_from_region<'a>( + region: &'a Region<'a>, +) -> impl Iterator> { + region + .entities + .values() + .map(named_entities_to_completion_item) +} + +fn named_entities_to_completion_item<'a>( + named_entities: &'a NamedEntities<'a>, +) -> CompletionItem<'a> { + match named_entities { + NamedEntities::Single(ent) => CompletionItem::Simple(ent), + NamedEntities::Overloaded(overloaded) => match overloaded.as_unique() { + None => CompletionItem::Overloaded(overloaded.designator().clone(), overloaded.len()), + Some(ent) => CompletionItem::Simple(ent), + }, + } +} diff --git a/vhdl_lang/src/completion/selected.rs b/vhdl_lang/src/completion/selected.rs new file mode 100644 index 00000000..f5dd12d7 --- /dev/null +++ b/vhdl_lang/src/completion/selected.rs @@ -0,0 +1,198 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::analysis::DesignRoot; +use crate::completion::region::completion_items_from_region; +use crate::data::Symbol; +use crate::syntax::Kind::All; +use crate::{named_entity, CompletionItem, EntRef, HasEntityId}; +use std::iter::once; + +/// Produces completions for a selected name, i.e., +/// `foo.` +/// The provided `ent` is the entity directly before the dot, i.e., +/// `foo` in the example above. +pub(crate) fn completions_for_selected_name<'b>( + root: &'b DesignRoot, + ent: EntRef<'b>, +) -> Vec> { + use crate::named_entity::AnyEntKind::*; + match ent.kind() { + Object(object) => completions_for_type(object.subtype.type_mark().kind()), + Design(design) => completions_for_design(design), + Library => ent + .library_name() + .map(|sym| list_primaries_for_lib(root, sym)) + .unwrap_or_default(), + _ => vec![], + } +} + +/// Returns completions applicable when calling `foo.` where `foo` is amn object of some type. +fn completions_for_type<'a>(typ: &'a named_entity::Type<'a>) -> Vec> { + use crate::named_entity::Type::*; + match typ { + Record(record_region) => record_region + .iter() + .map(|item| CompletionItem::Simple(item.ent)) + .collect(), + Alias(type_ent) => completions_for_type(type_ent.kind()), + Access(subtype) => { + let mut completions = completions_for_type(subtype.type_mark().kind()); + completions.push(CompletionItem::Keyword(All)); + completions + } + Protected(region, _) => completion_items_from_region(region).collect(), + _ => vec![], + } +} + +/// Returns completions applicable when calling `foo.` where `foo` is some design +/// (i.e. entity or package). +fn completions_for_design<'a>(design: &'a crate::Design<'a>) -> Vec> { + use crate::named_entity::Design::*; + match design { + Package(_, region) | PackageInstance(region) | InterfacePackageInstance(region) => { + completion_items_from_region(region) + .chain(once(CompletionItem::Keyword(All))) + .collect() + } + _ => vec![], + } +} + +/// List the name of all primary units for a given library. +/// If the library is non-resolvable, list an empty vector +fn list_primaries_for_lib<'a>(root: &'a DesignRoot, lib: &Symbol) -> Vec> { + let Some(lib) = root.get_lib(lib) else { + return vec![]; + }; + lib.primary_units() + .filter_map(|it| it.unit.get().and_then(|unit| unit.ent_id())) + .map(|id| CompletionItem::Simple(root.get_ent(id))) + .collect() +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::{assert_eq_unordered, LibraryBuilder}; + use crate::ast::Designator; + use crate::syntax::Kind::All; + use crate::{list_completion_options, CompletionItem}; + + #[test] + pub fn completes_selected_names() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libA", + "\ +package foo is + type my_record is record + abc: bit; + def: bit; + end record; + + constant y: my_record := ('1', '1'); + constant z: bit := y. +end foo; + ", + ); + + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("y.").end(); + let options = list_completion_options(&root, code.source(), cursor); + + let ent1 = root + .search_reference(code.source(), code.s1("abc").start()) + .unwrap(); + + let ent2 = root + .search_reference(code.source(), code.s1("def").start()) + .unwrap(); + + assert_eq_unordered( + &options, + &[CompletionItem::Simple(ent1), CompletionItem::Simple(ent2)], + ) + } + + #[test] + pub fn completing_primaries() { + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +use std. + +-- Need this so that the 'use std.' is associated to a context and can be analyzed correctly +package x is +end package x; +", + ); + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("use std.").end(); + let options = list_completion_options(&root, code.source(), cursor); + assert_eq_unordered( + &options, + &[ + CompletionItem::Simple(root.find_textio_pkg()), + CompletionItem::Simple(root.find_standard_pkg()), + CompletionItem::Simple(root.find_env_pkg()), + ], + ); + + let mut builder = LibraryBuilder::new(); + let code = builder.code( + "libname", + "\ +use std.t + +-- Need this so that the 'use std.' is associated to a context and can be analyzed correctly +package x is +end package x; +", + ); + let (root, _) = builder.get_analyzed_root(); + let cursor = code.s1("use std.t").end(); + let options = list_completion_options(&root, code.source(), cursor); + // Note that the filtering only happens at client side + assert_eq_unordered( + &options, + &[ + CompletionItem::Simple(root.find_textio_pkg()), + CompletionItem::Simple(root.find_standard_pkg()), + CompletionItem::Simple(root.find_env_pkg()), + ], + ); + } + + #[test] + pub fn completing_declarations() { + let mut input = LibraryBuilder::new(); + let code = input.code( + "libname", + "\ +use std.env. + +-- Need this package sp that the 'use std.' is associated and can be analyzed correctly +package x is +end package x; +", + ); + let (root, _) = input.get_analyzed_root(); + let cursor = code.s1("use std.env.").end(); + let options = list_completion_options(&root, code.source(), cursor); + + assert_eq_unordered( + &options, + &[ + CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("stop")), 2), + CompletionItem::Overloaded(Designator::Identifier(root.symbol_utf8("finish")), 2), + CompletionItem::Simple(root.find_env_symbol("resolution_limit")), + CompletionItem::Keyword(All), + ], + ); + } +} diff --git a/vhdl_lang/src/completion/tokenizer.rs b/vhdl_lang/src/completion/tokenizer.rs new file mode 100644 index 00000000..1d004a56 --- /dev/null +++ b/vhdl_lang/src/completion/tokenizer.rs @@ -0,0 +1,111 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this file, +// You can obtain one at http://mozilla.org/MPL/2.0/. +// +// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com +use crate::data::ContentReader; +use crate::syntax::{Symbols, Tokenizer}; +use crate::{Position, Source, Token}; + +/// Tokenizes `source` up to `cursor` but no further. The last token returned is the token +/// where the cursor currently resides or the token right before the cursor. +/// +/// Examples: +/// +/// input = "use ieee.std_logic_1|164.a" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} +/// +/// input = "use ieee.std_logic_1164|.a" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164} +/// +/// input = "use ieee.std_logic_1164.|a" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT} +/// input = "use ieee.std_logic_1164.a|" +/// ^ cursor position +/// `tokenize_input(input)` -> {USE, ieee, DOT, std_logic_1164, DOT, a} +/// +/// On error, or if the source is empty, returns an empty vector. +pub(crate) fn tokenize_input(symbols: &Symbols, source: &Source, cursor: Position) -> Vec { + let contents = source.contents(); + let mut tokenizer = Tokenizer::new(symbols, source, ContentReader::new(&contents)); + let mut tokens = Vec::new(); + loop { + match tokenizer.pop() { + Ok(Some(token)) => { + if token.pos.start() >= cursor { + break; + } + tokens.push(token); + } + Ok(None) => break, + Err(_) => return vec![], + } + } + tokens +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::syntax::test::Code; + use crate::syntax::Kind::*; + use assert_matches::assert_matches; + + macro_rules! kind { + ($kind: pat) => { + crate::syntax::Token { kind: $kind, .. } + }; + } + + #[test] + fn tokenizing_an_empty_input() { + let input = Code::new(""); + let tokens = tokenize_input(&input.symbols, input.source(), Position::new(0, 0)); + assert_eq!(tokens.len(), 0); + } + + #[test] + fn tokenizing_stops_at_the_cursors_position() { + let input = Code::new("use ieee.std_logic_1164.all"); + let mut cursor = input.s1("std_logic_11").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] + ); + cursor = input.s1("std_logic_1164").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [kind!(Use), kind!(Identifier), kind!(Dot), kind!(Identifier)] + ); + cursor = input.s1("std_logic_1164.").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [ + kind!(Use), + kind!(Identifier), + kind!(Dot), + kind!(Identifier), + kind!(Dot) + ] + ); + cursor = input.s1("std_logic_1164.all").pos().end(); + let tokens = tokenize_input(&input.symbols, input.source(), cursor); + assert_matches!( + tokens[..], + [ + kind!(Use), + kind!(Identifier), + kind!(Dot), + kind!(Identifier), + kind!(Dot), + kind!(All) + ] + ); + } +}