From 16d9e5ebcbd1b280fa6d9424f4a9356669887811 Mon Sep 17 00:00:00 2001 From: Lukas Scheller <45085299+Schottkyc137@users.noreply.github.com> Date: Sun, 30 Jun 2024 13:10:34 +0200 Subject: [PATCH] Improved completions (#313) This commit adds specific completions for selected names and attributes. It also extends the completion capabilities to entities, packages and package bodies (previously, this was only possible in architectures). Furthermore, all visible symbols from entities, packages and architectures are included for the generic completion provider. Previously, only symbols in the declarative part of architectures were includes. --- vhdl_lang/src/analysis/declarative.rs | 6 +- vhdl_lang/src/analysis/design_unit.rs | 29 +- vhdl_lang/src/analysis/root.rs | 2 +- vhdl_lang/src/analysis/tests/util.rs | 5 +- vhdl_lang/src/completion.rs | 974 +----------------- vhdl_lang/src/completion/attributes.rs | 188 ++++ .../src/completion/entity_instantiation.rs | 256 +++++ vhdl_lang/src/completion/generic.rs | 145 +++ vhdl_lang/src/completion/libraries.rs | 41 + vhdl_lang/src/completion/map_aspect.rs | 351 +++++++ vhdl_lang/src/completion/region.rs | 28 + vhdl_lang/src/completion/selected.rs | 198 ++++ vhdl_lang/src/completion/tokenizer.rs | 111 ++ vhdl_lang/src/named_entity/attribute.rs | 2 +- vhdl_lang/src/named_entity/design.rs | 10 +- vhdl_lang/src/named_entity/formal_region.rs | 4 +- vhdl_lang/src/named_entity/overloaded.rs | 2 +- vhdl_lang/src/named_entity/types.rs | 40 +- vhdl_lang/src/named_entity/visibility.rs | 12 +- vhdl_lang/src/syntax.rs | 1 + vhdl_lang/src/syntax/alias_declaration.rs | 3 +- vhdl_lang/src/syntax/attributes.rs | 5 +- vhdl_lang/src/syntax/component_declaration.rs | 9 +- vhdl_lang/src/syntax/concurrent_statement.rs | 25 +- vhdl_lang/src/syntax/configuration.rs | 13 +- vhdl_lang/src/syntax/context.rs | 17 +- vhdl_lang/src/syntax/declarative_part.rs | 3 +- vhdl_lang/src/syntax/design_unit.rs | 15 +- vhdl_lang/src/syntax/names.rs | 37 +- vhdl_lang/src/syntax/object_declaration.rs | 8 +- vhdl_lang/src/syntax/recover.rs | 106 ++ vhdl_lang/src/syntax/sequential_statement.rs | 58 +- vhdl_lang/src/syntax/subprogram.rs | 7 +- vhdl_lang/src/syntax/test.rs | 16 +- vhdl_lang/src/syntax/tokens/tokenizer.rs | 2 - vhdl_lang/src/syntax/type_declaration.rs | 11 +- vhdl_lang/src/syntax/view.rs | 5 +- vhdl_ls/src/vhdl_server.rs | 4 +- vhdl_ls/src/vhdl_server/completion.rs | 14 + vhdl_ls/src/vhdl_server/lifecycle.rs | 2 +- 40 files changed, 1717 insertions(+), 1048 deletions(-) create mode 100644 vhdl_lang/src/completion/attributes.rs create mode 100644 vhdl_lang/src/completion/entity_instantiation.rs create mode 100644 vhdl_lang/src/completion/generic.rs create mode 100644 vhdl_lang/src/completion/libraries.rs create mode 100644 vhdl_lang/src/completion/map_aspect.rs create mode 100644 vhdl_lang/src/completion/region.rs create mode 100644 vhdl_lang/src/completion/selected.rs create mode 100644 vhdl_lang/src/completion/tokenizer.rs create mode 100644 vhdl_lang/src/syntax/recover.rs 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(