diff --git a/vhdl_lang/src/analysis/declarative.rs b/vhdl_lang/src/analysis/declarative.rs index c84be55b..621fdb24 100644 --- a/vhdl_lang/src/analysis/declarative.rs +++ b/vhdl_lang/src/analysis/declarative.rs @@ -64,7 +64,7 @@ impl Declaration { | View(_) ), // LRM: package_body_declarative_item - AnyEntKind::Design(Design::PackageBody | Design::UninstPackage(..)) + AnyEntKind::Design(Design::PackageBody(..) | Design::UninstPackage(..)) | AnyEntKind::Overloaded( Overloaded::SubprogramDecl(_) | Overloaded::Subprogram(_) @@ -1173,11 +1173,11 @@ fn get_entity_class(ent: EntRef) -> Option { AnyEntKind::Library => None, AnyEntKind::Design(des) => match des { Design::Entity(_, _) => Some(EntityClass::Entity), - Design::Architecture(_) => Some(EntityClass::Architecture), + Design::Architecture(..) => Some(EntityClass::Architecture), Design::Configuration => Some(EntityClass::Configuration), Design::Package(_, _) => Some(EntityClass::Package), // Should never be target of attribute - Design::PackageBody => None, + Design::PackageBody(..) => None, Design::UninstPackage(_, _) => None, Design::PackageInstance(_) => None, Design::InterfacePackageInstance(_) => None, diff --git a/vhdl_lang/src/analysis/design_unit.rs b/vhdl_lang/src/analysis/design_unit.rs index f06664d8..a4a734e1 100644 --- a/vhdl_lang/src/analysis/design_unit.rs +++ b/vhdl_lang/src/analysis/design_unit.rs @@ -278,7 +278,11 @@ impl<'a, 't> AnalyzeContext<'a, 't> { self.ctx, &mut unit.ident, primary.into(), - AnyEntKind::Design(Design::Architecture(primary)), + AnyEntKind::Design(Design::Architecture( + Visibility::default(), + Region::default(), + primary, + )), src_span, Some(self.source()), ); @@ -294,6 +298,15 @@ impl<'a, 't> AnalyzeContext<'a, 't> { self.analyze_declarative_part(&scope, arch, &mut unit.decl, diagnostics)?; self.analyze_concurrent_part(&scope, arch, &mut unit.statements, diagnostics)?; scope.close(diagnostics); + + let region = scope.into_region(); + let visibility = root_scope.into_visibility(); + + unsafe { + arch.set_kind(AnyEntKind::Design(Design::Architecture( + visibility, region, primary, + ))) + } Ok(()) } @@ -334,7 +347,10 @@ impl<'a, 't> AnalyzeContext<'a, 't> { unit.ident.name().clone().into(), Some(self.work_library()), Related::DeclaredBy(primary.into()), - AnyEntKind::Design(Design::PackageBody), + AnyEntKind::Design(Design::PackageBody( + Visibility::default(), + Region::default(), + )), Some(unit.ident_pos(self.ctx).clone()), unit.span(), Some(self.source()), @@ -355,6 +371,10 @@ impl<'a, 't> AnalyzeContext<'a, 't> { self.analyze_declarative_part(&scope, body, &mut unit.decl, diagnostics)?; scope.close(diagnostics); + let region = scope.into_region(); + let visibility = root_scope.into_visibility(); + + unsafe { body.set_kind(AnyEntKind::Design(Design::PackageBody(visibility, region))) } Ok(()) } @@ -647,6 +667,11 @@ impl<'a, 't> AnalyzeContext<'a, 't> { "Use clause must be a selected name", ErrorCode::MismatchedKinds, ); + // We still want to resolve the name, + // so that it is available for completion purposes. + // We ignore the errors here, since there is already a diagnostic at that position. + let mut empty_diag = Vec::new(); + let _ = self.name_resolve(scope, name.span(), &mut name.item, &mut empty_diag); continue; } } diff --git a/vhdl_lang/src/analysis/root.rs b/vhdl_lang/src/analysis/root.rs index 6b85317b..3269fa68 100644 --- a/vhdl_lang/src/analysis/root.rs +++ b/vhdl_lang/src/analysis/root.rs @@ -475,7 +475,7 @@ impl DesignRoot { ) } // Find all architectures which implement the entity - AnyEntKind::Design(Design::Architecture(ent_of_arch)) => { + AnyEntKind::Design(Design::Architecture(.., ent_of_arch)) => { ent_of_arch.id == ent_id } _ => false, diff --git a/vhdl_lang/src/analysis/tests/util.rs b/vhdl_lang/src/analysis/tests/util.rs index 85e9bbcd..19f9062f 100644 --- a/vhdl_lang/src/analysis/tests/util.rs +++ b/vhdl_lang/src/analysis/tests/util.rs @@ -83,7 +83,10 @@ end architecture;" for (library_name, codes) in self.libraries.iter() { for code in codes { - root.add_design_file(library_name.clone(), code.design_file()); + root.add_design_file( + library_name.clone(), + code.design_file_diagnostics(&mut diagnostics), + ); } } root.analyze(&mut diagnostics); diff --git a/vhdl_lang/src/completion.rs b/vhdl_lang/src/completion.rs index 7ff5b98e..2582899d 100644 --- a/vhdl_lang/src/completion.rs +++ b/vhdl_lang/src/completion.rs @@ -5,19 +5,24 @@ // Copyright (c) 2023, Olof Kraigher olof.kraigher@gmail.com use crate::analysis::DesignRoot; -use crate::ast::search::{Found, FoundDeclaration, NotFinished, NotFound, SearchState, Searcher}; -use crate::ast::{ - AnyDesignUnit, AnyPrimaryUnit, ConcurrentStatement, Designator, MapAspect, ObjectClass, -}; -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, Value}; -use crate::{AnyEntKind, Design, EntRef, EntityId, HasTokenSpan, Overloaded, Position, Source}; -use std::collections::HashSet; -use std::default::Default; -use std::iter::once; -use vhdl_lang::ast::search::Finished; +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> { @@ -32,6 +37,11 @@ pub enum CompletionItem<'a> { Overloaded(Designator, usize), /// Complete a keyword Keyword(Kind), + /// Complete the 'work' library. + /// This is handled in a special manner because the + /// actual work library (using [CompletionItem::Simple] would complete the actual name + /// of the library, not the string 'work'. + Work, /// Entity instantiation, i.e., /// ```vhdl /// my_ent: entity work.foo @@ -46,444 +56,16 @@ pub enum CompletionItem<'a> { /// The second argument is a vector of architectures that are associated /// to this entity EntityInstantiation(EntRef<'a>, Vec>), + /// Complete an attribute designator (i.e. `'range`, `'stable`, ...) + Attribute(AttributeDesignator), } macro_rules! kind { ($kind: pat) => { - Token { kind: $kind, .. } + crate::syntax::Token { kind: $kind, .. } }; } -macro_rules! ident { - ($bind: pat) => { - Token { - kind: Identifier, - value: Value::Identifier($bind), - .. - } - }; -} - -#[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()))) - .collect() -} - -/// 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() -} - -/// Lists all available declarations for a primary unit inside a given library -/// If the library does not exist or there is no primary unit with the given name for that library, -/// return an empty vector -fn list_available_declarations<'a>( - root: &'a DesignRoot, - lib: &Symbol, - primary_unit: &Symbol, -) -> Vec> { - let Some(unit) = root - .get_lib(lib) - .and_then(|lib| lib.primary_unit(primary_unit)) - .and_then(|unit| unit.unit.get()) - else { - return vec![]; - }; - - match unit.data() { - AnyDesignUnit::Primary(AnyPrimaryUnit::Package(pkg)) => { - let Some(pkg_id) = pkg.ident.decl.get() else { - return Vec::default(); - }; - let ent = root.get_ent(pkg_id); - match &ent.kind { - AnyEntKind::Design(Design::Package(_, region)) => region - .entities - .values() - .map(|named_ent| match named_ent { - NamedEntities::Single(ent) => CompletionItem::Simple(ent), - NamedEntities::Overloaded(overloaded) => match overloaded.as_unique() { - None => CompletionItem::Overloaded( - overloaded.designator().clone(), - overloaded.len(), - ), - Some(ent_ref) => CompletionItem::Simple(ent_ref), - }, - }) - .chain(once(CompletionItem::Keyword(All))) - .collect(), - _ => Vec::default(), - } - } - _ => Vec::default(), - } -} - -/// General-purpose Completion Searcher -/// when no more accurate searcher is available. -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> Searcher for CompletionSearcher<'a> { - fn search_decl(&mut self, ctx: &dyn TokenAccess, decl: FoundDeclaration) -> SearchState { - match decl { - FoundDeclaration::Architecture(body) => { - // ensure we are in the concurrent region - if !ctx - .get_span(body.begin_token, body.get_end_token()) - .contains(self.cursor) - { - return Finished(NotFound); - } - // Add architecture declarations to the list of completed names - self.completions.extend( - body.decl - .iter() - .filter_map(|decl| decl.ent_id()) - .map(|eid| self.root.get_ent(eid)) - .filter(|ent| { - matches!( - ent.kind(), - AnyEntKind::Object(_) | AnyEntKind::ObjectAlias { .. } - ) - }) - .map(CompletionItem::Simple), - ); - - let Some(eid) = body.entity_name.reference.get() else { - return Finished(NotFound); - }; - let Some(ent) = DesignEnt::from_any(self.root.get_ent(eid)) else { - return Finished(NotFound); - }; - // Add ports and generics to the list of completed items - if let Design::Entity(_, region) = ent.kind() { - self.completions - .extend(region.entities.values().filter_map(|ent| { - if let NamedEntities::Single(ent) = ent { - Some(CompletionItem::Simple(ent)) - } else { - None - } - })) - } - // 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 Finished(NotFound); - } - } - self.completions.extend( - self.root - .get_visible_entities_from_entity(ent) - .map(|eid| entity_to_completion_item(self.root, eid)), - ); - Finished(Found) - } - _ => NotFinished, - } - } -} - -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 the 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. - /// This could, at some point, be further generalized into a generic - /// 'list visible entities of some kind of some generic design entity'. - pub fn get_visible_entities_from_entity( - &self, - ent: DesignEnt, - ) -> impl Iterator { - let mut entities: HashSet = HashSet::new(); - if let Design::Entity(vis, _) = 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()) - .map(|read_guard| read_guard.to_owned()) - .filter(|design_unit| design_unit.is_entity()) - .flat_map(|design_unit| design_unit.ent_id()); - entities.extend(itr); - } - _ => {} - } - } - } - // Remove entity that this was called from. - // Recursive instantiation is only possible with component instantiation. - entities.remove(&ent.id()); - entities.into_iter() - } -} - /// 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>( @@ -491,493 +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!(Use), ident!(library), kind!(Dot)] - | [.., kind!(Use), ident!(library), kind!(Dot), kind!(Identifier)] => { - list_primaries_for_lib(root, library) + [.., 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_for_selected_name(root, ent) + } else { + vec![] + } } - [.., kind!(Use), ident!(library), kind!(Dot), ident!(selected), kind!(Dot)] - | [.., kind!(Use), ident!(library), kind!(Dot), ident!(selected), kind!(Dot), kind!(StringLiteral | Identifier)] => { - list_available_declarations(root, library, selected) + [.., token, kind!(Tick)] | [.., token, kind!(Tick), kind!(Identifier)] => { + if let Some((_, ent)) = root.item_at_cursor(source, token.pos.start()) { + 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 - } - _ => { - 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; - - #[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 (root, _) = LibraryBuilder::new().get_analyzed_root(); - let code = Code::new("use std."); - let cursor = code.pos().end(); - let options = list_completion_options(&root, code.source(), cursor); - assert!(options.contains(&CompletionItem::Simple(root.find_textio_pkg()))); - assert!(options.contains(&CompletionItem::Simple(root.find_standard_pkg()))); - assert!(options.contains(&CompletionItem::Simple(root.find_env_pkg()))); - assert_eq!(options.len(), 3); - - let code = Code::new("use std.t"); - let cursor = code.pos().end(); - let options = list_completion_options(&root, code.source(), cursor); - // Note that the filtering only happens at client side - assert!(options.contains(&CompletionItem::Simple(root.find_textio_pkg()))); - assert!(options.contains(&CompletionItem::Simple(root.find_standard_pkg()))); - assert!(options.contains(&CompletionItem::Simple(root.find_env_pkg()))); - assert_eq!(options.len(), 3); - } - - #[test] - pub fn completing_declarations() { - let input = LibraryBuilder::new(); - let code = Code::new("use std.env."); - let (root, _) = input.get_analyzed_root(); - let cursor = code.pos().end(); - let options = list_completion_options(&root, code.source(), cursor); - - assert!(options.contains(&CompletionItem::Overloaded( - Designator::Identifier(root.symbol_utf8("stop")), - 2 - ))); - assert!(options.contains(&CompletionItem::Overloaded( - Designator::Identifier(root.symbol_utf8("finish")), - 2 - ))); - assert!(options.contains(&CompletionItem::Simple( - root.find_env_symbol("resolution_limit"), - ))); - assert!(options.contains(&CompletionItem::Keyword(All))); - assert_eq!(options.len(), 4); - } - - #[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_eq_unordered( - &options, - &[ - CompletionItem::Simple(clk_signal), - CompletionItem::Simple(rst_signal), - ], - ); - let cursor = code - .s1("port map ( - clk => c") - .pos() - .end(); - let options = list_completion_options(&root, code.source(), cursor); - assert_eq_unordered( - &options, - &[ - CompletionItem::Simple(clk_signal), - 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_eq_unordered( - &options[..], - &[ - CompletionItem::EntityInstantiation(my_ent, vec![]), - 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_eq_unordered( - &options[..], - &[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_eq_unordered( - &options[..], - &[ - CompletionItem::EntityInstantiation(my_ent2, vec![]), - 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(); - - match &options[..] { - [CompletionItem::EntityInstantiation(got_ent, architectures)] => { - assert_eq!(*got_ent, ent); - assert_eq_unordered(architectures, &[arch1, arch2]); - } - _ => panic!("Expected entity instantiation"), + completions_for_map_aspect(root, cursor, source) } - } - - #[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_eq_unordered( - &options, - &[CompletionItem::Simple(ent1), CompletionItem::Simple(ent2)], - ) + _ => 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..1497de5f --- /dev/null +++ b/vhdl_lang/src/completion/entity_instantiation.rs @@ -0,0 +1,256 @@ +// 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 that are visible from a VHDL architecture and return +/// these entities as completion item. +pub(crate) fn get_visible_entities_from_architecture<'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] + 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..d2cb9f2e --- /dev/null +++ b/vhdl_lang/src/completion/generic.rs @@ -0,0 +1,145 @@ +// 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_architecture; +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> { + /// Add entity instantiation completions that are visible from within an architecture body + 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_architecture(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..b860561b --- /dev/null +++ b/vhdl_lang/src/completion/libraries.rs @@ -0,0 +1,41 @@ +// 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::syntax::test::assert_eq_unordered; + use crate::{list_completion_options, CompletionItem}; + + #[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_unordered( + &options, + &[ + CompletionItem::Simple( + root.get_ent(root.get_lib(&root.symbol_utf8("std")).unwrap().id()), + ), + CompletionItem::Work, + ], + ) + } +} diff --git a/vhdl_lang/src/completion/map_aspect.rs b/vhdl_lang/src/completion/map_aspect.rs new file mode 100644 index 00000000..1203b615 --- /dev/null +++ b/vhdl_lang/src/completion/map_aspect.rs @@ -0,0 +1,351 @@ +// 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_component_instantiation_map() { + 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))); + 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..bc4d6ae8 --- /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 so that the 'use std.env.' is associated to a context 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) + ] + ); + } +} diff --git a/vhdl_lang/src/named_entity/attribute.rs b/vhdl_lang/src/named_entity/attribute.rs index eedba350..1c311da7 100644 --- a/vhdl_lang/src/named_entity/attribute.rs +++ b/vhdl_lang/src/named_entity/attribute.rs @@ -14,7 +14,7 @@ use super::TypeEnt; #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct AttributeEnt<'a> { - ent: EntRef<'a>, + pub ent: EntRef<'a>, } impl<'a> AttributeEnt<'a> { diff --git a/vhdl_lang/src/named_entity/design.rs b/vhdl_lang/src/named_entity/design.rs index 2f632688..a430dec2 100644 --- a/vhdl_lang/src/named_entity/design.rs +++ b/vhdl_lang/src/named_entity/design.rs @@ -17,10 +17,10 @@ pub enum Design<'a> { Entity(Visibility<'a>, Region<'a>), /// A VHDL architecture. /// The linked `DesignEnt` is the entity that belongs to the architecture. - Architecture(DesignEnt<'a>), + Architecture(Visibility<'a>, Region<'a>, DesignEnt<'a>), Configuration, Package(Visibility<'a>, Region<'a>), - PackageBody, + PackageBody(Visibility<'a>, Region<'a>), UninstPackage(Visibility<'a>, Region<'a>), /// An instantiated Package, i.e., /// ```vhdl @@ -45,7 +45,7 @@ impl<'a> Design<'a> { Architecture(..) => "architecture", Configuration => "configuration", Package(..) => "package", - PackageBody => "package body", + PackageBody(..) => "package body", UninstPackage(..) => "uninstantiated package", PackageInstance(_) | InterfacePackageInstance(_) => "package instance", Context(..) => "context", @@ -58,7 +58,7 @@ impl<'a> Design<'a> { pub struct DesignEnt<'a>(pub EntRef<'a>); impl<'a> DesignEnt<'a> { - pub fn from_any(ent: &'a AnyEnt) -> Option> { + pub fn from_any(ent: &'a AnyEnt<'a>) -> Option> { if matches!(ent.kind(), AnyEntKind::Design(..)) { Some(DesignEnt(ent)) } else { @@ -66,7 +66,7 @@ impl<'a> DesignEnt<'a> { } } - pub fn kind(&self) -> &Design<'a> { + pub fn kind(&self) -> &'a Design<'a> { if let AnyEntKind::Design(typ) = self.0.kind() { typ } else { diff --git a/vhdl_lang/src/named_entity/formal_region.rs b/vhdl_lang/src/named_entity/formal_region.rs index b1c7270c..1cd5e198 100644 --- a/vhdl_lang/src/named_entity/formal_region.rs +++ b/vhdl_lang/src/named_entity/formal_region.rs @@ -271,10 +271,10 @@ impl<'a> RecordRegion<'a> { } } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct RecordElement<'a> { // A record element declaration - ent: EntRef<'a>, + pub ent: EntRef<'a>, } impl<'a> RecordElement<'a> { diff --git a/vhdl_lang/src/named_entity/overloaded.rs b/vhdl_lang/src/named_entity/overloaded.rs index 34878290..1043cd74 100644 --- a/vhdl_lang/src/named_entity/overloaded.rs +++ b/vhdl_lang/src/named_entity/overloaded.rs @@ -229,7 +229,7 @@ impl<'a> SignatureKey<'a> { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct OverloadedEnt<'a> { - ent: EntRef<'a>, + pub ent: EntRef<'a>, } impl<'a> OverloadedEnt<'a> { diff --git a/vhdl_lang/src/named_entity/types.rs b/vhdl_lang/src/named_entity/types.rs index 526d17a2..070c6952 100644 --- a/vhdl_lang/src/named_entity/types.rs +++ b/vhdl_lang/src/named_entity/types.rs @@ -31,7 +31,7 @@ pub enum Type<'a> { // Incomplete type will be overwritten when full type is found Incomplete, Subtype(Subtype<'a>), - // The region of the protected type which needs to be extendend by the body + // The region of the protected type which needs to be extended by the body Protected(Region<'a>, bool), File, Interface, @@ -64,6 +64,29 @@ impl<'a> Type<'a> { Type::Universal(univ) => univ.describe(), } } + + pub fn is_scalar(&self) -> bool { + use Type::*; + matches!(self, Enum(_) | Integer | Real | Physical | Universal(_)) + } + + pub fn is_discrete(&self) -> bool { + use Type::*; + matches!( + self, + Integer | Enum(_) | Universal(UniversalType::Integer) | Physical + ) + } + + pub fn is_physical(&self) -> bool { + use Type::*; + matches!(self, Physical) + } + + pub fn is_array(&self) -> bool { + use Type::*; + matches!(self, Array { .. }) + } } impl UniversalType { @@ -330,10 +353,7 @@ impl<'a> BaseType<'a> { } pub fn is_scalar(&self) -> bool { - matches!( - self.kind(), - Type::Enum(_) | Type::Integer | Type::Real | Type::Physical | Type::Universal(_) - ) + self.kind().is_scalar() } pub fn is_compatible_with_string_literal(&self) -> bool { @@ -393,17 +413,11 @@ impl<'a> BaseType<'a> { } pub fn is_discrete(&self) -> bool { - matches!( - self.kind(), - Type::Integer - | Type::Enum(_) - | Type::Universal(UniversalType::Integer) - | Type::Physical - ) + self.kind().is_discrete() } pub fn is_physical(&self) -> bool { - matches!(self.kind(), Type::Physical) + self.kind().is_physical() } pub fn is_closely_related(&self, other: BaseType<'a>) -> bool { diff --git a/vhdl_lang/src/named_entity/visibility.rs b/vhdl_lang/src/named_entity/visibility.rs index de82b1a3..4c0a612a 100644 --- a/vhdl_lang/src/named_entity/visibility.rs +++ b/vhdl_lang/src/named_entity/visibility.rs @@ -25,7 +25,7 @@ impl<'a> VisibleEntity<'a> { } #[derive(Clone)] -struct VisibleRegion<'a> { +pub struct VisibleRegion<'a> { // The position where the entity was made visible visible_pos: Vec>, region: &'a Region<'a>, @@ -38,6 +38,10 @@ impl<'a> VisibleRegion<'a> { more.visible_pos.push(visible_pos.cloned()); more } + + pub fn region(&self) -> &Region<'a> { + self.region + } } #[derive(Clone, Default)] @@ -59,6 +63,10 @@ impl<'a> Visibility<'a> { }); } + pub fn all_in_region(&self) -> impl Iterator> { + return self.all_in_regions.iter(); + } + pub fn visible(&self) -> impl Iterator> + '_ { self.visible.values().flatten().map(|entry| entry.1.entity) } @@ -91,7 +99,7 @@ impl<'a> Visibility<'a> { ent: &'a AnyEnt, ) { // Add implicit declarations when using declaration - // For example all enum literals are made implicititly visible when using an enum type + // For example all enum literals are made implicitly visible when using an enum type for entity in ent.as_actual().implicits.iter() { self.make_potentially_visible_with_name( visible_pos, diff --git a/vhdl_lang/src/syntax.rs b/vhdl_lang/src/syntax.rs index f8b20df6..37dd123c 100644 --- a/vhdl_lang/src/syntax.rs +++ b/vhdl_lang/src/syntax.rs @@ -29,6 +29,7 @@ mod subtype_indication; mod type_declaration; mod waveform; +mod recover; #[cfg(test)] pub mod test; mod view; diff --git a/vhdl_lang/src/syntax/alias_declaration.rs b/vhdl_lang/src/syntax/alias_declaration.rs index f7389fc4..2b18cb48 100644 --- a/vhdl_lang/src/syntax/alias_declaration.rs +++ b/vhdl_lang/src/syntax/alias_declaration.rs @@ -11,6 +11,7 @@ use super::subtype_indication::parse_subtype_indication; use super::tokens::{Kind::*, TokenSpan}; use crate::ast::token_range::WithTokenSpan; use crate::ast::{AliasDeclaration, WithDecl}; +use crate::syntax::recover::expect_semicolon_or_last; use vhdl_lang::syntax::parser::ParsingContext; pub fn parse_alias_declaration( @@ -37,7 +38,7 @@ pub fn parse_alias_declaration( } }; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(WithTokenSpan::new( AliasDeclaration { diff --git a/vhdl_lang/src/syntax/attributes.rs b/vhdl_lang/src/syntax/attributes.rs index 032b7161..3a40b72f 100644 --- a/vhdl_lang/src/syntax/attributes.rs +++ b/vhdl_lang/src/syntax/attributes.rs @@ -14,6 +14,7 @@ use crate::ast::{ Attribute, AttributeDeclaration, AttributeSpecification, Designator, EntityClass, EntityName, EntityTag, WithRef, }; +use crate::syntax::recover::expect_semicolon_or_last; use vhdl_lang::syntax::parser::ParsingContext; fn parse_entity_class(ctx: &mut ParsingContext<'_>) -> ParseResult { @@ -87,7 +88,7 @@ pub fn parse_attribute(ctx: &mut ParsingContext<'_>) -> ParseResult { let type_mark = parse_type_mark(ctx)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); vec![WithTokenSpan::new(Attribute::Declaration(AttributeDeclaration { ident: ident.into(), type_mark, @@ -99,7 +100,7 @@ pub fn parse_attribute(ctx: &mut ParsingContext<'_>) -> ParseResult { ctx.stream.skip(); let new_list = parse_generic_interface_list(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); if list.is_some() { ctx.diagnostics .push(Diagnostic::syntax_error(token, "Duplicate generic clause")); @@ -48,7 +49,7 @@ pub fn parse_optional_port_list( Port => { ctx.stream.skip(); let new_list = parse_port_interface_list(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); if list.is_some() { ctx.diagnostics .push(Diagnostic::syntax_error(token, "Duplicate port clause")); @@ -59,7 +60,7 @@ pub fn parse_optional_port_list( Generic => { ctx.stream.skip(); parse_generic_interface_list(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); ctx.diagnostics.push(Diagnostic::syntax_error( token, "Generic clause must come before port clause", @@ -88,7 +89,7 @@ pub fn parse_component_declaration( ctx.stream.pop_if_kind(Component); } let end_ident = ctx.stream.pop_optional_ident(); - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(ComponentDeclaration { span: TokenSpan::new(start_token, end_token), diff --git a/vhdl_lang/src/syntax/concurrent_statement.rs b/vhdl_lang/src/syntax/concurrent_statement.rs index f43cf6e1..142278f8 100644 --- a/vhdl_lang/src/syntax/concurrent_statement.rs +++ b/vhdl_lang/src/syntax/concurrent_statement.rs @@ -22,6 +22,7 @@ use super::waveform::{parse_delay_mechanism, parse_waveform}; use crate::ast::token_range::WithTokenSpan; use crate::ast::*; use crate::data::*; +use crate::syntax::recover::{expect_semicolon, expect_semicolon_or_last}; use crate::syntax::{Kind, TokenAccess}; use crate::TokenId; use vhdl_lang::syntax::parser::ParsingContext; @@ -53,7 +54,7 @@ pub fn parse_block_statement( ctx.stream.expect_kind(End)?; ctx.stream.expect_kind(Block)?; let end_ident = ctx.stream.pop_optional_ident(); - let end_tok = ctx.stream.expect_kind(SemiColon)?; + let end_tok = expect_semicolon_or_last(ctx); Ok(BlockStatement { guard_condition, header, @@ -94,7 +95,7 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult )); } let (list, closing_paren) = parse_association_list(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); if generic_map.is_none() { generic_map = Some(MapAspect { start: token_id, @@ -113,7 +114,7 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult .push(Diagnostic::syntax_error(token, "Duplicate generic clause")); } let parsed_generic_list = parse_generic_interface_list(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); if generic_clause.is_none() { generic_clause = Some(parsed_generic_list); } @@ -134,7 +135,7 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult )); } let (list, closing_paren) = parse_association_list(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); if port_map.is_none() { port_map = Some(MapAspect { start: token_id, @@ -153,7 +154,7 @@ fn parse_block_header(ctx: &mut ParsingContext<'_>) -> ParseResult .push(Diagnostic::syntax_error(token, "Duplicate port clause")); } let parsed_port_list = parse_port_interface_list(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); if port_clause.is_none() { port_clause = Some(parsed_port_list); } @@ -230,7 +231,7 @@ pub fn parse_process_statement( } ctx.stream.expect_kind(Process)?; let end_ident = ctx.stream.pop_optional_ident(); - let end_tok = ctx.stream.expect_kind(SemiColon)?; + let end_tok = expect_semicolon_or_last(ctx); Ok(ProcessStatement { postponed: postponed.is_some(), sensitivity_list, @@ -367,7 +368,7 @@ pub fn parse_instantiation_statement( ) -> ParseResult { let (generic_map, port_map) = parse_generic_and_port_map(ctx)?; - let end_tok = ctx.stream.expect_kind(SemiColon)?; + let end_tok = expect_semicolon_or_last(ctx); Ok(InstantiationStatement { unit, @@ -411,7 +412,7 @@ fn parse_generate_body( alternative_label.as_ref().map(|label| &label.tree), Some(end_ident), ); - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); } let body = GenerateBody { @@ -438,7 +439,7 @@ fn parse_for_generate_statement( ctx.stream.expect_kind(End)?; ctx.stream.expect_kind(Generate)?; let end_ident = ctx.stream.pop_optional_ident(); - let end_tok = ctx.stream.expect_kind(SemiColon)?; + let end_tok = expect_semicolon_or_last(ctx); Ok(ForGenerateStatement { index_name, @@ -508,7 +509,7 @@ fn parse_if_generate_statement( ctx.stream.expect_kind(Generate)?; let end_ident = ctx.stream.pop_optional_ident(); - let end_tok = ctx.stream.expect_kind(SemiColon)?; + let end_tok = expect_semicolon_or_last(ctx); Ok(IfGenerateStatement { conds: Conditionals { @@ -559,7 +560,7 @@ fn parse_case_generate_statement( ctx.stream.expect_kind(Generate)?; let end_ident = ctx.stream.pop_optional_ident(); - let end_tok = ctx.stream.expect_kind(SemiColon)?; + let end_tok = expect_semicolon_or_last(ctx); Ok(CaseGenerateStatement { sels: Selection { @@ -625,7 +626,7 @@ pub fn parse_concurrent_statement( With => ConcurrentStatement::Assignment(parse_selected_signal_assignment(ctx, true)?), _ => { let target = parse_name(ctx)?.map_into(Target::Name); - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); ConcurrentStatement::ProcedureCall(to_procedure_call(ctx, target, true)?) } } diff --git a/vhdl_lang/src/syntax/configuration.rs b/vhdl_lang/src/syntax/configuration.rs index 919f44d2..2c6111a8 100644 --- a/vhdl_lang/src/syntax/configuration.rs +++ b/vhdl_lang/src/syntax/configuration.rs @@ -13,6 +13,7 @@ use super::tokens::{Kind::*, TokenSpan}; use crate::ast::token_range::WithTokenSpan; use crate::ast::*; use crate::data::*; +use crate::syntax::recover::{expect_semicolon, expect_semicolon_or_last}; use vhdl_lang::syntax::parser::ParsingContext; /// LRM 7.3.2.2 @@ -45,7 +46,7 @@ fn parse_binding_indication_known_entity_aspect( ) -> ParseResult { let (generic_map, port_map) = parse_generic_and_port_map(ctx)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); Ok(BindingIndication { entity_aspect, generic_map, @@ -102,7 +103,7 @@ fn parse_component_configuration_known_spec( ); ctx.stream.expect_kind(For)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); Ok(ComponentConfiguration { spec, bind_ind, @@ -214,7 +215,7 @@ fn parse_block_configuration_known_name( ); } ctx.stream.expect_kind(For)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); Ok(BlockConfiguration { block_spec, use_clauses, @@ -295,7 +296,7 @@ pub fn parse_configuration_declaration( ctx.stream.expect_kind(End)?; ctx.stream.pop_if_kind(Configuration); let end_ident = ctx.stream.pop_optional_ident(); - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(ConfigurationDeclaration { span: TokenSpan::new(start_token, end_token), @@ -321,7 +322,7 @@ pub fn parse_configuration_specification( let vunit_bind_inds = parse_vunit_binding_indication_list_known_keyword(ctx)?; ctx.stream.expect_kind(End)?; ctx.stream.expect_kind(For)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(ConfigurationSpecification { span: TokenSpan::new(start_token, end_token), spec, @@ -331,7 +332,7 @@ pub fn parse_configuration_specification( } else { if ctx.stream.skip_if_kind(End) { ctx.stream.expect_kind(For)?; - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); } let end_token = ctx.stream.get_last_token_id(); Ok(ConfigurationSpecification { diff --git a/vhdl_lang/src/syntax/context.rs b/vhdl_lang/src/syntax/context.rs index 8409d239..cd34e58e 100644 --- a/vhdl_lang/src/syntax/context.rs +++ b/vhdl_lang/src/syntax/context.rs @@ -11,8 +11,9 @@ 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_ident_list, parse_name_list}; - #[derive(PartialEq, Debug)] pub enum DeclarationOrReference { Declaration(ContextDeclaration), @@ -23,7 +24,7 @@ pub enum DeclarationOrReference { pub fn parse_library_clause(ctx: &mut ParsingContext<'_>) -> ParseResult { let library_token = ctx.stream.expect_kind(Library)?; let name_list = parse_ident_list(ctx)?; - let semi_token = ctx.stream.expect_kind(SemiColon)?; + let semi_token = recover::expect_semicolon_or_last(ctx); Ok(LibraryClause { span: TokenSpan::new(library_token, semi_token), name_list, @@ -35,13 +36,13 @@ pub fn parse_use_clause(ctx: &mut ParsingContext<'_>) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult match parse_context(ctx) { Ok(DeclarationOrReference::Declaration(context_decl)) => { if !context_clause.is_empty() { - let mut diagnostic = Diagnostic::syntax_error(context_decl.ident.pos(ctx), "Context declaration may not be preceeded by a context clause"); + let mut diagnostic = Diagnostic::syntax_error(context_decl.ident.pos(ctx), "Context declaration may not be preceded by a context clause"); for context_item in context_clause.iter() { diagnostic.add_related(context_item.get_pos(ctx.stream), context_item_message(context_item, "may not come before context declaration")); @@ -869,7 +870,7 @@ end entity; diagnostics, vec![Diagnostic::syntax_error( code.s1("ctx"), - "Context declaration may not be preceeded by a context clause", + "Context declaration may not be preceded by a context clause", ) .related( code.s1("library lib;"), diff --git a/vhdl_lang/src/syntax/names.rs b/vhdl_lang/src/syntax/names.rs index 54922fc8..c0b26d46 100644 --- a/vhdl_lang/src/syntax/names.rs +++ b/vhdl_lang/src/syntax/names.rs @@ -278,7 +278,13 @@ fn parse_attribute_name( name: WithTokenSpan, signature: Option>, ) -> ParseResult> { - let attr = ctx.stream.expect_attribute_designator()?; + let attr = match ctx.stream.expect_attribute_designator() { + Ok(desi) => desi, + Err(e) => { + ctx.diagnostics.push(e); + return Ok(name); + } + }; let (expression, span) = { if ctx.stream.skip_if_kind(LeftPar) { @@ -403,7 +409,16 @@ fn _parse_name(ctx: &mut ParsingContext<'_>) -> ParseResult> match token.kind { Dot => { ctx.stream.skip(); - let suffix = parse_suffix(ctx)?; + // This is the situation where we have, for example, + // use work. + // For completion purposes, we want to preserve the 'work' symbol + let suffix = match parse_suffix(ctx) { + Ok(suffix) => suffix, + Err(diagnostic) => { + ctx.diagnostics.push(diagnostic); + return Ok(name); + } + }; let span = name.span.end_with(suffix.token); match suffix.item { @@ -1271,4 +1286,22 @@ mod tests { ); assert_eq!(list.0.items, vec![code.s1("a => b").association_element()]); } + + #[test] + fn separated_name_with_semicolon_diagnostic() { + // We still want to be able to resolve a + let code = Code::new("work."); + let (name, diag) = code.with_stream_diagnostics(parse_name); + assert_eq!( + diag, + vec![Diagnostic::syntax_error(code.eof_pos(), "Unexpected EOF",)] + ); + assert_eq!( + name, + WithTokenSpan::new( + Name::Designator(code.s1("work").ref_designator().item), + code.s1("work").token_span() + ) + ); + } } diff --git a/vhdl_lang/src/syntax/object_declaration.rs b/vhdl_lang/src/syntax/object_declaration.rs index 3fbc1116..b6a6ef9b 100644 --- a/vhdl_lang/src/syntax/object_declaration.rs +++ b/vhdl_lang/src/syntax/object_declaration.rs @@ -3,15 +3,15 @@ // You can obtain one at http://mozilla.org/MPL/2.0/. // // Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com - +/// LRM 6.4.2 Object Declarations use super::common::ParseResult; use super::expression::parse_expression; use super::names::parse_identifier_list; use super::subtype_indication::parse_subtype_indication; use super::tokens::{Kind::*, TokenSpan}; use crate::ast::token_range::WithTokenSpan; -/// LRM 6.4.2 Object Declarations use crate::ast::*; +use crate::syntax::recover::expect_semicolon_or_last; use crate::Diagnostic; use vhdl_lang::syntax::parser::ParsingContext; @@ -51,7 +51,7 @@ fn parse_object_declaration_kind( ctx.stream.expect_kind(Colon)?; let subtype = parse_subtype_indication(ctx)?; let opt_expression = parse_optional_assignment(ctx)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(idents .into_iter() @@ -108,7 +108,7 @@ pub fn parse_file_declaration( None } }; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); // If the `file_open_information` is present, `file_name` is mandatory // LRM 6.4.2.5 diff --git a/vhdl_lang/src/syntax/recover.rs b/vhdl_lang/src/syntax/recover.rs new file mode 100644 index 00000000..e8c5f37e --- /dev/null +++ b/vhdl_lang/src/syntax/recover.rs @@ -0,0 +1,106 @@ +// 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 +/// Module for robust parsing +use crate::syntax::parser::ParsingContext; +use crate::syntax::Kind::{Colon, SemiColon}; +use crate::syntax::{kinds_error, kinds_str}; +use crate::TokenId; + +/// Special handling when expecting a semicolon. +/// When the next token is +/// * a semicolon, then consume that token and produce no error +/// * a token that could be confused with a semicolon (i.e., a comma), +/// then consume that token and report an error +/// * none of these choices: do not consume the token and report an error +pub fn expect_semicolon(ctx: &mut ParsingContext<'_>) -> Option { + let token = match ctx.stream.peek_expect() { + Ok(token) => token, + Err(err) => { + ctx.diagnostics + .push(err.when(format!("expecting {}", kinds_str(&[SemiColon])))); + return None; + } + }; + match token.kind { + SemiColon => { + ctx.stream.skip(); + Some(ctx.stream.get_last_token_id()) + } + Colon => { + ctx.stream.skip(); + ctx.diagnostics + .push(kinds_error(token.pos.clone(), &[SemiColon])); + Some(ctx.stream.get_last_token_id()) + } + _ => { + ctx.diagnostics + .push(kinds_error(ctx.stream.pos_before(token), &[SemiColon])); + None + } + } +} + +/// Expect the next token to be a SemiColon, or return the last token. +/// The behavior is the same as [expect_semicolon]. +#[must_use] +pub fn expect_semicolon_or_last(ctx: &mut ParsingContext<'_>) -> TokenId { + expect_semicolon(ctx).unwrap_or(ctx.stream.get_last_token_id()) +} + +#[cfg(test)] +mod tests { + use crate::analysis::tests::Code; + use crate::ast::token_range::WithTokenSpan; + use crate::ast::Declaration; + use crate::syntax::declarative_part::parse_declarative_part; + use crate::syntax::test::check_diagnostics; + use vhdl_lang::ast::{ObjectClass, ObjectDeclaration}; + use vhdl_lang::Diagnostic; + + #[test] + fn recover_from_semicolon_in_declarative_path() { + let code = Code::new( + "\ +signal x : std_logic := a. +signal y: bit; +", + ); + let (declarations, diagnostics) = code.parse_ok(parse_declarative_part); + assert_eq!( + declarations, + vec![ + WithTokenSpan::new( + Declaration::Object(ObjectDeclaration { + class: ObjectClass::Signal, + ident: code.s1("x").decl_ident(), + subtype_indication: code.s1("std_logic").subtype_indication(), + expression: Some(code.s1("a.").s1("a").expr()) + }), + code.s1("signal x : std_logic := a.").token_span() + ), + WithTokenSpan::new( + Declaration::Object(ObjectDeclaration { + class: ObjectClass::Signal, + ident: code.s1("y").decl_ident(), + subtype_indication: code.s1("bit").subtype_indication(), + expression: None + }), + code.s1("signal y: bit;").token_span() + ), + ] + ); + check_diagnostics( + diagnostics, + vec![ + Diagnostic::syntax_error( + code.s1(".").pos().pos_at_end(), + "Expected '{identifier}', '{character}', '{string}' or 'all'", + ), + Diagnostic::syntax_error(code.s1(".").pos().pos_at_end(), "Expected ';'"), + ], + ); + } +} diff --git a/vhdl_lang/src/syntax/sequential_statement.rs b/vhdl_lang/src/syntax/sequential_statement.rs index d25b0241..c4a8dd2b 100644 --- a/vhdl_lang/src/syntax/sequential_statement.rs +++ b/vhdl_lang/src/syntax/sequential_statement.rs @@ -16,6 +16,8 @@ use crate::ast::token_range::WithTokenSpan; use crate::ast::*; use crate::data::*; use crate::syntax::common::check_label_identifier_mismatch; +use crate::syntax::kinds_error; +use crate::syntax::recover::{expect_semicolon, expect_semicolon_or_last}; use crate::HasTokenSpan; use vhdl_lang::syntax::parser::ParsingContext; use vhdl_lang::TokenSpan; @@ -36,7 +38,7 @@ fn parse_wait_statement(ctx: &mut ParsingContext<'_>) -> ParseResult) -> ParseResult) -> ParseResult { ctx.stream.expect_kind(Loop)?; let end_label_pos = check_label_identifier_mismatch(ctx, label, ctx.stream.pop_optional_ident()); - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); Ok(LoopStatement { iteration_scheme, statements, @@ -246,7 +248,7 @@ fn parse_next_statement(ctx: &mut ParsingContext<'_>) -> ParseResult) -> ParseResult) -> ParseResult) -> ParseResult, { let item = parse_item(ctx)?; - - expect_token!( - ctx.stream, - token, + let token = ctx.stream.peek_expect()?; + match token.kind { When => { - Ok(AssignmentRightHand::Conditional(parse_conditonals(ctx, item, parse_item)?)) - - }, + ctx.stream.skip(); + Ok(AssignmentRightHand::Conditional(parse_conditonals( + ctx, item, parse_item, + )?)) + } SemiColon => { + ctx.stream.skip(); Ok(AssignmentRightHand::Simple(item)) } - ) + Colon => { + ctx.stream.skip(); + ctx.diagnostics + .push(kinds_error(token.pos.clone(), &[SemiColon, When])); + Ok(AssignmentRightHand::Simple(item)) + } + _ => { + ctx.diagnostics.push(kinds_error( + ctx.stream.pos_before(token), + &[SemiColon, When], + )); + Ok(AssignmentRightHand::Simple(item)) + } + } } fn parse_conditonals( @@ -442,7 +458,7 @@ fn parse_assignment_or_procedure_call( Release => { ctx.stream.skip(); let force_mode = parse_optional_force_mode(ctx)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); SequentialStatement::SignalReleaseAssignment(SignalReleaseAssignment { target, @@ -525,7 +541,6 @@ fn parse_unlabeled_sequential_statement( label: Option<&Ident>, ) -> ParseResult { let token = ctx.stream.peek_expect()?; - ctx.stream.get_current_token_id(); let statement = { try_init_token_kind!( token, @@ -542,7 +557,7 @@ fn parse_unlabeled_sequential_statement( Return => SequentialStatement::Return(parse_return_statement(ctx)?), Null => { ctx.stream.skip(); - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); SequentialStatement::Null }, With => { @@ -597,9 +612,8 @@ pub fn parse_sequential_statement( mod tests { use super::*; use crate::ast::{DelayMechanism, Ident}; - use pretty_assertions::assert_eq; - use crate::syntax::test::Code; + use pretty_assertions::assert_eq; fn parse(code: &str) -> (Code, LabeledSequentialStatement) { let code = Code::new(code); diff --git a/vhdl_lang/src/syntax/subprogram.rs b/vhdl_lang/src/syntax/subprogram.rs index 9d60206e..2fd0cdfe 100644 --- a/vhdl_lang/src/syntax/subprogram.rs +++ b/vhdl_lang/src/syntax/subprogram.rs @@ -16,6 +16,7 @@ use crate::data::*; use crate::syntax::concurrent_statement::parse_map_aspect; use crate::syntax::interface_declaration::parse_generic_interface_list; use crate::syntax::names::parse_name; +use crate::syntax::recover::expect_semicolon_or_last; use vhdl_lang::syntax::parser::ParsingContext; pub fn parse_signature(ctx: &mut ParsingContext<'_>) -> ParseResult> { @@ -130,7 +131,7 @@ pub fn parse_subprogram_instantiation( None }; let generic_map = parse_map_aspect(ctx, Generic)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(SubprogramInstantiation { span: TokenSpan::new(start_token, end_token), kind, @@ -206,7 +207,7 @@ pub fn parse_subprogram_declaration( ) -> ParseResult { let start_token = ctx.stream.get_current_token_id(); let specification = parse_subprogram_specification(ctx)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(SubprogramDeclaration { span: TokenSpan::new(start_token, end_token), @@ -241,7 +242,7 @@ pub fn parse_subprogram_body( } else { None }; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(SubprogramBody { span: TokenSpan::new(specification_start_token, end_token), diff --git a/vhdl_lang/src/syntax/test.rs b/vhdl_lang/src/syntax/test.rs index 54941cc7..51ab6c5e 100644 --- a/vhdl_lang/src/syntax/test.rs +++ b/vhdl_lang/src/syntax/test.rs @@ -221,7 +221,7 @@ impl Code { self.s_from_start(substr, 1) } - /// Returns the code in between the begin and end + /// Returns the code in between the beginning and end /// ``` /// let code = Code::new("foo bar 123 baz foobar"); /// assert_eq!(code.between("bar", "baz"), Code::new("bar 123 baz")); @@ -760,6 +760,14 @@ impl Code { self.parse_ok_no_diagnostics(parse_design_file) } + pub fn design_file_diagnostics(&self, diagnostics: &mut dyn DiagnosticHandler) -> DesignFile { + let (file, new_diagnostics) = self.parse_ok(parse_design_file); + for diag in new_diagnostics { + diagnostics.push(diag) + } + file + } + pub fn architecture_body(&self) -> ArchitectureBody { self.parse_ok_no_diagnostics(parse_architecture_body) } @@ -945,12 +953,6 @@ impl AsRef for Code { } } -/* impl AsRef for Code { - fn as_ref(&self) -> &TokenSpan { - &self.token_span() - } -} */ - fn value_to_string(value: &Value) -> String { match value { Value::Identifier(ident) => ident.name_utf8(), diff --git a/vhdl_lang/src/syntax/tokens/tokenizer.rs b/vhdl_lang/src/syntax/tokens/tokenizer.rs index 0b72595c..51a380e5 100644 --- a/vhdl_lang/src/syntax/tokens/tokenizer.rs +++ b/vhdl_lang/src/syntax/tokens/tokenizer.rs @@ -2225,8 +2225,6 @@ my_other_ident", ); } - // @TODO test incorrect value for base, ex: 2x"ff" - // @TODO test incorrect value for length ex: 2x"1111" #[test] fn tokenize_bit_string_literal() { use BaseSpecifier::{B, D, O, SB, SO, SX, UB, UO, UX, X}; diff --git a/vhdl_lang/src/syntax/type_declaration.rs b/vhdl_lang/src/syntax/type_declaration.rs index ae277bd8..e0508b08 100644 --- a/vhdl_lang/src/syntax/type_declaration.rs +++ b/vhdl_lang/src/syntax/type_declaration.rs @@ -16,6 +16,7 @@ use crate::ast::*; use crate::ast::{AbstractLiteral, Range}; use crate::named_entity::Reference; use crate::syntax::names::parse_type_mark; +use crate::syntax::recover::{expect_semicolon, expect_semicolon_or_last}; use vhdl_lang::syntax::parser::ParsingContext; /// LRM 5.2.2 Enumeration types @@ -85,7 +86,7 @@ fn parse_record_type_definition( let idents = parse_identifier_list(ctx)?; ctx.stream.expect_kind(Colon)?; let subtype = parse_subtype_indication(ctx)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); for ident in idents { elem_decls.push(ElementDeclaration { ident: ident.into(), @@ -101,7 +102,7 @@ pub fn parse_subtype_declaration(ctx: &mut ParsingContext<'_>) -> ParseResult ParseResult<(TypeDefinition, Option)> { let primary_unit = WithDecl::new(ctx.stream.expect_ident()?); - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); let mut secondary_units = Vec::new(); @@ -172,7 +173,7 @@ fn parse_physical_type_definition( }; secondary_units.push((ident, literal)); - ctx.stream.expect_kind(SemiColon)?; + expect_semicolon(ctx); } ) } @@ -275,7 +276,7 @@ pub fn parse_type_declaration(ctx: &mut ParsingContext<'_>) -> ParseResult parse_enumeration_type_definition(ctx)? ); - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(TypeDeclaration { span: TokenSpan::new(start_token, end_token), ident, diff --git a/vhdl_lang/src/syntax/view.rs b/vhdl_lang/src/syntax/view.rs index 644d1d70..ea9cc090 100644 --- a/vhdl_lang/src/syntax/view.rs +++ b/vhdl_lang/src/syntax/view.rs @@ -10,6 +10,7 @@ use crate::syntax::common::ParseResult; use crate::syntax::interface_declaration::parse_optional_mode; use crate::syntax::names::parse_name; use crate::syntax::parser::ParsingContext; +use crate::syntax::recover::expect_semicolon_or_last; use crate::syntax::separated_list::parse_ident_list; use crate::syntax::subtype_indication::parse_subtype_indication; use crate::syntax::Kind::*; @@ -36,7 +37,7 @@ pub(crate) fn parse_mode_view_declaration( ctx.stream.expect_kind(View)?; let end_ident_pos = check_end_identifier_mismatch(ctx, &ident.tree, ctx.stream.pop_optional_ident()); - let end_tok = ctx.stream.expect_kind(SemiColon)?; + let end_tok = expect_semicolon_or_last(ctx); Ok(WithTokenSpan::new( ModeViewDeclaration { ident, @@ -56,7 +57,7 @@ pub(crate) fn parse_mode_view_element_definition( let element_list = parse_ident_list(ctx)?; ctx.stream.expect_kind(Colon)?; let mode = parse_element_mode_indication(ctx)?; - let end_token = ctx.stream.expect_kind(SemiColon)?; + let end_token = expect_semicolon_or_last(ctx); Ok(ModeViewElement { span: TokenSpan::new(start, end_token), mode, diff --git a/vhdl_ls/src/vhdl_server.rs b/vhdl_ls/src/vhdl_server.rs index 6b6655bc..7f675abb 100644 --- a/vhdl_ls/src/vhdl_server.rs +++ b/vhdl_ls/src/vhdl_server.rs @@ -596,10 +596,10 @@ fn to_symbol_kind(kind: &AnyEntKind) -> SymbolKind { AnyEntKind::View(_) => SymbolKind::INTERFACE, AnyEntKind::Design(d) => match d { vhdl_lang::Design::Entity(_, _) => SymbolKind::MODULE, - vhdl_lang::Design::Architecture(_) => SymbolKind::MODULE, + vhdl_lang::Design::Architecture(..) => SymbolKind::MODULE, vhdl_lang::Design::Configuration => SymbolKind::MODULE, vhdl_lang::Design::Package(_, _) => SymbolKind::PACKAGE, - vhdl_lang::Design::PackageBody => SymbolKind::PACKAGE, + vhdl_lang::Design::PackageBody(..) => SymbolKind::PACKAGE, vhdl_lang::Design::UninstPackage(_, _) => SymbolKind::PACKAGE, vhdl_lang::Design::PackageInstance(_) => SymbolKind::PACKAGE, vhdl_lang::Design::InterfacePackageInstance(_) => SymbolKind::PACKAGE, diff --git a/vhdl_ls/src/vhdl_server/completion.rs b/vhdl_ls/src/vhdl_server/completion.rs index c59c64b5..29ee65a5 100644 --- a/vhdl_ls/src/vhdl_server/completion.rs +++ b/vhdl_ls/src/vhdl_server/completion.rs @@ -13,6 +13,13 @@ impl VHDLServer { ) -> lsp_types::CompletionItem { match item { vhdl_lang::CompletionItem::Simple(ent) => entity_to_completion_item(ent), + vhdl_lang::CompletionItem::Work => CompletionItem { + label: "work".to_string(), + detail: Some("work library".to_string()), + kind: Some(CompletionItemKind::MODULE), + insert_text: Some("work".to_string()), + ..Default::default() + }, vhdl_lang::CompletionItem::Formal(ent) => { let mut item = entity_to_completion_item(ent); if self.client_supports_snippets() { @@ -110,6 +117,13 @@ impl VHDLServer { ..Default::default() } } + vhdl_lang::CompletionItem::Attribute(attribute) => CompletionItem { + label: format!("{attribute}"), + detail: Some(format!("{attribute}")), + insert_text: Some(format!("{attribute}")), + kind: Some(CompletionItemKind::REFERENCE), + ..Default::default() + }, } } diff --git a/vhdl_ls/src/vhdl_server/lifecycle.rs b/vhdl_ls/src/vhdl_server/lifecycle.rs index 47c7da4c..f61d2b76 100644 --- a/vhdl_ls/src/vhdl_server/lifecycle.rs +++ b/vhdl_ls/src/vhdl_server/lifecycle.rs @@ -55,7 +55,7 @@ impl VHDLServer { self.apply_initial_options(options) } self.init_params = Some(init_params); - let trigger_chars: Vec = r".".chars().map(|ch| ch.to_string()).collect(); + let trigger_chars: Vec = r"'.".chars().map(|ch| ch.to_string()).collect(); let capabilities = ServerCapabilities { text_document_sync: Some(TextDocumentSyncCapability::Kind(