Skip to content

Commit

Permalink
Merge pull request #201 from Schottkyc137/autocomplete-instantiation
Browse files Browse the repository at this point in the history
Initial support for instantiation completions
  • Loading branch information
kraigher authored Oct 13, 2023
2 parents f5aa115 + 9dbd8c0 commit 34883b5
Show file tree
Hide file tree
Showing 6 changed files with 2,738 additions and 9 deletions.
253 changes: 249 additions & 4 deletions vhdl_lang/src/analysis/completion.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
use crate::analysis::DesignRoot;
use crate::ast::{AnyDesignUnit, AnyPrimaryUnit, Declaration, UnitKey};
use crate::ast::visitor::{Visitor, VisitorResult};
use crate::ast::{
AnyDesignUnit, AnyPrimaryUnit, AnySecondaryUnit, ComponentDeclaration, Declaration,
EntityDeclaration, InstantiationStatement, InterfaceDeclaration, MapAspect, PackageDeclaration,
SubprogramDeclaration, UnitKey,
};
use crate::data::{ContentReader, Symbol};
use crate::syntax::Kind::*;
use crate::syntax::{Symbols, Token, Tokenizer, Value};
use crate::{Position, Source};
use crate::syntax::{Symbols, Token, TokenAccess, Tokenizer, Value};
use crate::{EntityId, Position, Source};
use itertools::Itertools;
use std::collections::HashSet;
use std::default::Default;

macro_rules! kind {
Expand All @@ -23,6 +29,193 @@ macro_rules! ident {
};
}

#[derive(Eq, PartialEq, Debug)]
enum MapAspectKind {
Port,
Generic,
}

/// 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`.
/// After walking the AST, the ports or generics are written to the `items` vector.
/// The `kind` member chooses whether to select ports or generics.
struct PortsOrGenericsExtractor<'a> {
id: EntityId,
items: &'a mut Vec<String>,
kind: MapAspectKind,
}

impl DesignRoot {
fn extract_port_or_generic_names(
&self,
id: EntityId,
items: &mut Vec<String>,
kind: MapAspectKind,
) {
let mut searcher = PortsOrGenericsExtractor { id, items, kind };
self.walk(&mut searcher);
}
}

impl<'a> Visitor for PortsOrGenericsExtractor<'a> {
fn visit_component_declaration(
&mut self,
node: &ComponentDeclaration,
_ctx: &dyn TokenAccess,
) -> VisitorResult {
if node.ident.decl != Some(self.id) {
return VisitorResult::Skip;
}
if self.kind == MapAspectKind::Port {
for port in &node.port_list {
self.items.push(port.completable_name())
}
}
if self.kind == MapAspectKind::Generic {
for generic in &node.generic_list {
self.items.push(generic.completable_name())
}
}
VisitorResult::Stop
}

fn visit_entity_declaration(
&mut self,
node: &EntityDeclaration,
_ctx: &dyn TokenAccess,
) -> VisitorResult {
if node.ident.decl != Some(self.id) {
return VisitorResult::Skip;
}
if self.kind == MapAspectKind::Port {
if let Some(ports) = &node.port_clause {
for port in ports {
self.items.push(port.completable_name())
}
}
}
if self.kind == MapAspectKind::Generic {
if let Some(generics) = &node.generic_clause {
for generic in generics {
self.items.push(generic.completable_name())
}
}
}
VisitorResult::Stop
}

fn visit_package_declaration(
&mut self,
node: &PackageDeclaration,
_ctx: &dyn TokenAccess,
) -> VisitorResult {
if node.ident.decl != Some(self.id) {
return VisitorResult::Skip;
}
if self.kind == MapAspectKind::Generic {
if let Some(generics) = &node.generic_clause {
for generic in generics {
self.items.push(generic.completable_name())
}
}
}
VisitorResult::Stop
}
}

impl InterfaceDeclaration {
/// Returns completable names for an interface declarations.
/// Example:
/// `signal my_signal : natural := 5` => `my_signal`
fn completable_name(&self) -> String {
match self {
InterfaceDeclaration::Object(obj) => obj.ident.tree.to_string(),
InterfaceDeclaration::File(file) => file.ident.to_string(),
InterfaceDeclaration::Type(typ) => typ.tree.item.name().to_string(),
InterfaceDeclaration::Subprogram(decl, _) => match decl {
SubprogramDeclaration::Procedure(proc) => proc.designator.to_string(),
SubprogramDeclaration::Function(func) => func.designator.to_string(),
},
InterfaceDeclaration::Package(package) => package.package_name.to_string(),
}
}
}

/// Visitor responsible for completions in selected AST elements
struct AutocompletionVisitor<'a> {
root: &'a DesignRoot,
cursor: Position,
completions: &'a mut Vec<String>,
}

impl<'a> AutocompletionVisitor<'a> {
/// 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,
node: &InstantiationStatement,
map: &MapAspect,
ctx: &dyn TokenAccess,
kind: MapAspectKind,
) -> bool {
if !map.span(ctx).contains(self.cursor) {
return false;
}
let formals_in_map: HashSet<String> =
HashSet::from_iter(map.formals().map(|name| name.to_string().to_lowercase()));
if let Some(ent) = node.entity_reference() {
self.root
.extract_port_or_generic_names(ent, self.completions, kind);
self.completions
.retain(|name| !formals_in_map.contains(&name.to_lowercase()));
}
true
}
}

impl<'a> Visitor for AutocompletionVisitor<'a> {
/// Visit an instantiation statement extracting completions for ports or generics.
fn visit_instantiation_statement(
&mut self,
node: &InstantiationStatement,
ctx: &dyn TokenAccess,
) -> VisitorResult {
if let Some(map) = &node.generic_map {
if self.load_completions_for_map_aspect(node, map, ctx, MapAspectKind::Generic) {
return VisitorResult::Stop;
}
}
if let Some(map) = &node.port_map {
if self.load_completions_for_map_aspect(node, map, ctx, MapAspectKind::Port) {
return VisitorResult::Stop;
}
}
VisitorResult::Skip
}

// preliminary optimizations: only visit architecture
fn visit_any_primary_unit(
&mut self,
_node: &AnyPrimaryUnit,
_ctx: &dyn TokenAccess,
) -> VisitorResult {
VisitorResult::Skip
}

// preliminary optimizations: only visit architecture
fn visit_any_secondary_unit(
&mut self,
node: &AnySecondaryUnit,
_ctx: &dyn TokenAccess,
) -> VisitorResult {
match node {
AnySecondaryUnit::Architecture(_) => VisitorResult::Continue,
AnySecondaryUnit::PackageBody(_) => VisitorResult::Skip,
}
}
}

/// Returns the completable string representation of a declaration
/// for example:
/// `let alias = parse_vhdl("alias my_alias is ...")`
Expand Down Expand Up @@ -147,7 +340,16 @@ impl DesignRoot {
| [.., kind!(Use), ident!(library), kind!(Dot), ident!(selected), kind!(Dot), kind!(StringLiteral | Identifier)] => {
self.list_available_declarations(library, selected)
}
_ => vec![],
_ => {
let mut completions = vec![];
let mut visitor = AutocompletionVisitor {
completions: &mut completions,
root: self,
cursor,
};
self.walk(&mut visitor);
completions
}
}
}
}
Expand Down Expand Up @@ -243,4 +445,47 @@ mod test {
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options, vec!["stop", "finish", "resolution_limit", "all"])
}

#[test]
pub fn completing_instantiation_statement() {
let mut input = LibraryBuilder::new();
let code = input.code(
"libname",
"\
entity my_ent is
end entity my_ent;
architecture arch of my_ent is
component comp is
generic (
A: natural := 5;
B: integer
);
port (
clk : in bit;
rst : in bit;
dout : out bit
);
end component comp;
signal clk, rst: bit;
begin
comp_inst: comp
generic map (
A => 2
)
port map (
clk => clk
);
end arch;
",
);
let (root, _) = input.get_analyzed_root();
let cursor = code.s1("generic map (").pos().end();
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options, vec!["B"]);

let cursor = code.s1("port map (").pos().end();
let options = root.list_completion_options(code.source(), cursor);
assert_eq!(options, vec!["rst", "dout"]);
}
}
4 changes: 4 additions & 0 deletions vhdl_lang/src/analysis/lock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,10 @@ impl<'a, T, R> ReadGuard<'a, T, R> {
pub fn result(&self) -> &R {
self.guard.result.as_ref().unwrap()
}

pub fn data(&self) -> &T {
&self.guard.data
}
}

impl<'a, T, R> std::ops::Deref for ReadGuard<'a, T, R> {
Expand Down
23 changes: 18 additions & 5 deletions vhdl_lang/src/analysis/root.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use super::standard::UniversalTypes;
use super::visibility::Visibility;

use crate::ast::search::*;
use crate::ast::visitor::{walk, Visitor};
use crate::ast::*;
use crate::data::*;
use crate::syntax::{Symbols, Token, TokenAccess};
Expand Down Expand Up @@ -580,6 +581,18 @@ impl DesignRoot {
NotFound
}

pub fn walk(&self, visitor: &mut impl Visitor) {
for library in self.libraries.values() {
for unit_id in library.sorted_unit_ids() {
let unit = library.units.get(unit_id.key()).unwrap();
let tokens = &unit.tokens;
if let Some(unit) = unit.unit.get() {
walk(unit.data(), visitor, tokens);
}
}
}
}

pub fn search_library(
&self,
library_name: &Symbol,
Expand Down Expand Up @@ -1200,21 +1213,21 @@ package pkg is new gpkg generic map (const => foo);
vec![
Diagnostic::error(
code.s("pkg", 2),
"A primary unit has already been declared with name 'pkg' in library 'libname'"
"A primary unit has already been declared with name 'pkg' in library 'libname'",
).related(code.s("pkg", 1), "Previously defined here"),
Diagnostic::error(
code.s("entname", 2),
"A primary unit has already been declared with name 'entname' in library 'libname'"
"A primary unit has already been declared with name 'entname' in library 'libname'",
).related(code.s("entname", 1), "Previously defined here"),
Diagnostic::error(
code.s("pkg", 3),
"A primary unit has already been declared with name 'pkg' in library 'libname'"
"A primary unit has already been declared with name 'pkg' in library 'libname'",
).related(code.s("pkg", 1), "Previously defined here"),
Diagnostic::error(
code.s("pkg", 4),
"A primary unit has already been declared with name 'pkg' in library 'libname'"
"A primary unit has already been declared with name 'pkg' in library 'libname'",
).related(code.s("pkg", 1), "Previously defined here"),
]
],
);
}

Expand Down
Loading

0 comments on commit 34883b5

Please sign in to comment.