diff --git a/vhdl_lang/src/analysis/concurrent.rs b/vhdl_lang/src/analysis/concurrent.rs index 7ed7b7ea..af3c6e76 100644 --- a/vhdl_lang/src/analysis/concurrent.rs +++ b/vhdl_lang/src/analysis/concurrent.rs @@ -2,7 +2,7 @@ // 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) 2019, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com // These fields are better explicit than .. since we are forced to consider if new fields should be searched #![allow(clippy::unneeded_field_pattern)] @@ -65,6 +65,7 @@ impl<'a> AnalyzeContext<'a> { sensitivity_list, decl, statements, + source_range: _, } = process; if let Some(sensitivity_list) = sensitivity_list { match sensitivity_list { diff --git a/vhdl_lang/src/analysis/design_unit.rs b/vhdl_lang/src/analysis/design_unit.rs index 42a36eb1..e21aa51f 100644 --- a/vhdl_lang/src/analysis/design_unit.rs +++ b/vhdl_lang/src/analysis/design_unit.rs @@ -2,7 +2,7 @@ // 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) 2019, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::*; use crate::ast::*; @@ -75,14 +75,15 @@ impl<'a> AnalyzeContext<'a> { ); if let Some(ref mut list) = unit.generic_clause { - self.analyze_interface_list(&mut primary_region, list, diagnostics)?; + self.analyze_interface_list(&mut primary_region, &mut list.item, diagnostics)?; } if let Some(ref mut list) = unit.port_clause { - self.analyze_interface_list(&mut primary_region, list, diagnostics)?; + self.analyze_interface_list(&mut primary_region, &mut list.item, diagnostics)?; + } + self.analyze_declarative_part(&mut primary_region, &mut unit.decl.item, diagnostics)?; + if let Some(ref mut statements) = unit.statements { + self.analyze_concurrent_part(&mut primary_region, &mut statements.item, diagnostics)?; } - self.analyze_declarative_part(&mut primary_region, &mut unit.decl, diagnostics)?; - self.analyze_concurrent_part(&mut primary_region, &mut unit.statements, diagnostics)?; - *region = primary_region.without_parent(); Ok(()) @@ -148,9 +149,9 @@ impl<'a> AnalyzeContext<'a> { ); if let Some(ref mut list) = unit.generic_clause { - self.analyze_interface_list(&mut primary_region, list, diagnostics)?; + self.analyze_interface_list(&mut primary_region, &mut list.item, diagnostics)?; } - self.analyze_declarative_part(&mut primary_region, &mut unit.decl, diagnostics)?; + self.analyze_declarative_part(&mut primary_region, &mut unit.decl.item, diagnostics)?; if !self.has_package_body() { primary_region.close(diagnostics); @@ -241,8 +242,8 @@ impl<'a> AnalyzeContext<'a> { )), ); - self.analyze_declarative_part(&mut region, &mut unit.decl, diagnostics)?; - self.analyze_concurrent_part(&mut region, &mut unit.statements, diagnostics)?; + self.analyze_declarative_part(&mut region, &mut unit.decl.item, diagnostics)?; + self.analyze_concurrent_part(&mut region, &mut unit.statements.item, diagnostics)?; region.close(diagnostics); Ok(()) } @@ -278,7 +279,7 @@ impl<'a> AnalyzeContext<'a> { let mut region = Region::extend(&package.result().region, Some(&root_region)); - self.analyze_declarative_part(&mut region, &mut unit.decl, diagnostics)?; + self.analyze_declarative_part(&mut region, &mut unit.decl.item, diagnostics)?; region.close(diagnostics); Ok(()) } diff --git a/vhdl_lang/src/analysis/root.rs b/vhdl_lang/src/analysis/root.rs index 353a4074..a8089223 100644 --- a/vhdl_lang/src/analysis/root.rs +++ b/vhdl_lang/src/analysis/root.rs @@ -387,10 +387,10 @@ impl DesignRoot { let result = AnalysisData { diagnostics, + has_circular_dependency, root_region, region, ent, - has_circular_dependency, }; unit.finish(result) diff --git a/vhdl_lang/src/analysis/target.rs b/vhdl_lang/src/analysis/target.rs index fb813f83..d3c4984f 100644 --- a/vhdl_lang/src/analysis/target.rs +++ b/vhdl_lang/src/analysis/target.rs @@ -90,10 +90,10 @@ pub enum AssignmentType { } impl AssignmentType { - fn to_str(&self) -> &str { + fn to_str(self) -> String { match self { - AssignmentType::Signal => "signal", - AssignmentType::Variable => "variable", + AssignmentType::Signal => String::from("signal"), + AssignmentType::Variable => String::from("variable"), } } } diff --git a/vhdl_lang/src/analysis/visibility.rs b/vhdl_lang/src/analysis/visibility.rs index 02ff8db1..f094c6e7 100644 --- a/vhdl_lang/src/analysis/visibility.rs +++ b/vhdl_lang/src/analysis/visibility.rs @@ -203,12 +203,11 @@ impl<'a> Visible<'a> { ); fn last_visible_pos(visible_entity: &VisibleEntityRef) -> u32 { - for pos in visible_entity.visible_pos.iter().rev() { - if let Some(pos) = pos { - return pos.range().start.line; - } + if let Some(pos) = visible_entity.visible_pos.iter().flatten().last() { + pos.range().start.line + } else { + 0 } - 0 } // Sort by last visible pos to make error messages and testing deterministic @@ -216,13 +215,11 @@ impl<'a> Visible<'a> { visible_entities.sort_by_key(|ent| last_visible_pos(*ent)); for visible_entity in visible_entities { - for visible_pos in visible_entity.visible_pos.iter().rev() { - if let Some(pos) = visible_pos { - error.add_related( - pos, - format!("Conflicting name '{}' made visible here", designator), - ); - } + for visible_pos in visible_entity.visible_pos.iter().rev().flatten() { + error.add_related( + visible_pos, + format!("Conflicting name '{}' made visible here", designator), + ); } if let Some(pos) = visible_entity.entity.decl_pos() { error.add_related( diff --git a/vhdl_lang/src/ast.rs b/vhdl_lang/src/ast.rs index 7666a0b4..14a76112 100644 --- a/vhdl_lang/src/ast.rs +++ b/vhdl_lang/src/ast.rs @@ -3,7 +3,7 @@ // This Source Code Form is subject to the terms of the Mozilla Public // You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com // Allowing this, since box_patterns are feature gated: https://github.com/rust-lang/rfcs/pull/469 // Track here: https://github.com/rust-lang/rust/issues/29641 @@ -873,6 +873,9 @@ pub struct BlockStatement { pub header: BlockHeader, pub decl: Vec, pub statements: Vec, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 11.2 Block statement @@ -897,6 +900,9 @@ pub struct ProcessStatement { pub sensitivity_list: Option, pub decl: Vec, pub statements: Vec, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 11.4 Concurrent procedure call statements @@ -1011,6 +1017,9 @@ pub enum ContextItem { pub struct ContextDeclaration { pub ident: Ident, pub items: ContextClause, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 4.9 Package instatiation declaration @@ -1020,6 +1029,9 @@ pub struct PackageInstantiation { pub ident: Ident, pub package_name: WithPos, pub generic_map: Option>, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 7.3 Configuration specification @@ -1107,6 +1119,9 @@ pub struct ConfigurationDeclaration { pub decl: Vec, pub vunit_bind_inds: Vec, pub block_config: BlockConfiguration, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 3.2 Entity declarations @@ -1114,10 +1129,13 @@ pub struct ConfigurationDeclaration { pub struct EntityDeclaration { pub context_clause: ContextClause, pub ident: Ident, - pub generic_clause: Option>, - pub port_clause: Option>, - pub decl: Vec, - pub statements: Vec, + pub generic_clause: Option>>, + pub port_clause: Option>>, + pub decl: WithPos>, + pub statements: Option>>, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 3.3 Architecture bodies @@ -1126,8 +1144,11 @@ pub struct ArchitectureBody { pub context_clause: ContextClause, pub ident: Ident, pub entity_name: WithRef, - pub decl: Vec, - pub statements: Vec, + pub decl: WithPos>, + pub statements: WithPos>, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 4.7 Package declarations @@ -1135,8 +1156,11 @@ pub struct ArchitectureBody { pub struct PackageDeclaration { pub context_clause: ContextClause, pub ident: Ident, - pub generic_clause: Option>, - pub decl: Vec, + pub generic_clause: Option>>, + pub decl: WithPos>, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 4.8 Package bodies @@ -1144,7 +1168,10 @@ pub struct PackageDeclaration { pub struct PackageBody { pub context_clause: ContextClause, pub ident: WithRef, - pub decl: Vec, + pub decl: WithPos>, + + // Non-LRM fields + pub source_range: SrcPos, } /// LRM 13.1 Design units diff --git a/vhdl_lang/src/ast/display.rs b/vhdl_lang/src/ast/display.rs index 1bedb75a..5afb9e05 100644 --- a/vhdl_lang/src/ast/display.rs +++ b/vhdl_lang/src/ast/display.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com //! Implementation of Display @@ -1039,7 +1039,7 @@ impl Display for EntityDeclaration { if let Some(generic_clause) = &self.generic_clause { let mut first = true; - for generic in generic_clause { + for generic in &generic_clause.item { if first { write!(f, "\n generic (\n {}", generic)?; } else { @@ -1054,7 +1054,7 @@ impl Display for EntityDeclaration { if let Some(port_clause) = &self.port_clause { let mut first = true; - for port in port_clause { + for port in &port_clause.item { if first { write!(f, "\n port (\n {}", port)?; } else { @@ -1078,7 +1078,7 @@ impl Display for PackageDeclaration { write!(f, "package {} is", self.ident)?; let mut first = true; - for generic in generic_clause { + for generic in &generic_clause.item { if first { write!(f, "\n generic (\n {}", generic)?; } else { diff --git a/vhdl_lang/src/ast/name_util.rs b/vhdl_lang/src/ast/name_util.rs index 639b083a..c626fe10 100644 --- a/vhdl_lang/src/ast/name_util.rs +++ b/vhdl_lang/src/ast/name_util.rs @@ -332,12 +332,8 @@ impl FunctionCall { ref name, ref parameters, } = self; - - if let Some(indexes) = assoc_elems_to_indexes(parameters) { - Some(Name::Indexed(Box::new(name.clone()), indexes)) - } else { - None - } + assoc_elems_to_indexes(parameters) + .map(|indexes| Name::Indexed(Box::new(name.clone()), indexes)) } } diff --git a/vhdl_lang/src/ast/search.rs b/vhdl_lang/src/ast/search.rs index 7a47d8c4..b3efc7ad 100644 --- a/vhdl_lang/src/ast/search.rs +++ b/vhdl_lang/src/ast/search.rs @@ -3,7 +3,7 @@ // This Source Code Form is subject to the terms of the Mozilla Public // You can obtain one at http://mozilla.org/MPL/2.0/. // -// Copyright (c) 2019, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com // These fields are better explicit than .. since we are forced to consider if new fields should be searched #![allow(clippy::unneeded_field_pattern)] @@ -134,6 +134,13 @@ impl Search for Vec { } } +impl Search for WithPos> { + fn search(&self, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(&self.item.search(searcher)); + NotFound + } +} + impl Search for Option { fn search(&self, searcher: &mut impl Searcher) -> SearchResult { for decl in self.iter() { @@ -423,6 +430,7 @@ impl Search for LabeledConcurrentStatement { sensitivity_list, decl, statements, + source_range: _, } = process; return_if_found!(sensitivity_list.search(searcher)); return_if_found!(decl.search(searcher)); diff --git a/vhdl_lang/src/data/contents.rs b/vhdl_lang/src/data/contents.rs index 5a9b9a74..5cd2cdaf 100644 --- a/vhdl_lang/src/data/contents.rs +++ b/vhdl_lang/src/data/contents.rs @@ -136,14 +136,13 @@ fn split_lines(code: &str) -> Vec { while i < bytes.len() { let byte = bytes[i]; + i += 1; if byte == b'\n' { - i += 1; let line = bytes[start..i].to_owned(); let line = unsafe { String::from_utf8_unchecked(line) }; lines.push(line); start = i; } else if byte == b'\r' { - i += 1; let mut line = bytes[start..i].to_owned(); let last = line.len().saturating_sub(1); line[last] = b'\n'; @@ -155,8 +154,6 @@ fn split_lines(code: &str) -> Vec { } start = i; - } else { - i += 1; } } diff --git a/vhdl_lang/src/data/diagnostic.rs b/vhdl_lang/src/data/diagnostic.rs index cf4cd616..1e9b4c42 100644 --- a/vhdl_lang/src/data/diagnostic.rs +++ b/vhdl_lang/src/data/diagnostic.rs @@ -72,7 +72,7 @@ impl Diagnostic { pub fn drain_related(&mut self) -> Vec { let mut diagnostics = Vec::with_capacity(self.related.len()); - let related = std::mem::replace(&mut self.related, Vec::new()); + let related = std::mem::take(&mut self.related); for (pos, msg) in related { diagnostics.push(Diagnostic::new( pos, diff --git a/vhdl_lang/src/data/source.rs b/vhdl_lang/src/data/source.rs index 67f4faae..54360abf 100644 --- a/vhdl_lang/src/data/source.rs +++ b/vhdl_lang/src/data/source.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::contents::Contents; use super::diagnostic::{Diagnostic, DiagnosticResult}; @@ -515,6 +515,21 @@ impl SrcPos { } } + /// Combines two lexical positions into a larger lexical position between both. + /// The file name is assumed to be the same. + pub fn combine_into_between(self, other: &dyn AsRef) -> Self { + let other = other.as_ref(); + debug_assert!(self.source == other.source, "Assumes sources are equal"); + + let start = min(self.range.end, other.range.end); + let end = max(self.range.start, other.range.start); + + SrcPos { + source: self.source, + range: Range { start, end }, + } + } + pub fn start(&self) -> Position { self.range.start } diff --git a/vhdl_lang/src/data/symbol_table.rs b/vhdl_lang/src/data/symbol_table.rs index 467c55d3..e2fde70d 100644 --- a/vhdl_lang/src/data/symbol_table.rs +++ b/vhdl_lang/src/data/symbol_table.rs @@ -100,12 +100,7 @@ impl SymbolTable { /// and `None` otherwise. pub fn lookup(&self, name: &Latin1String) -> Option { let name_to_symbol = self.name_to_symbol.read(); - if let Some(sym) = name_to_symbol.get(name) { - // Symbol already exists with identical case - Some(sym.clone()) - } else { - None - } + name_to_symbol.get(name).cloned() } /// Inserts a basic identifier and returns a corresponding `Symbol` instance. diff --git a/vhdl_lang/src/lib.rs b/vhdl_lang/src/lib.rs index 012d8038..d430667b 100644 --- a/vhdl_lang/src/lib.rs +++ b/vhdl_lang/src/lib.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com #![allow(clippy::upper_case_acronyms)] // False positives with unconditional loops // allow for now @@ -20,7 +20,7 @@ mod syntax; pub use crate::config::Config; pub use crate::data::{ Diagnostic, Latin1String, Message, MessageHandler, MessagePrinter, MessageType, Position, - Range, Severity, Source, SrcPos, + Range, Severity, Source, SrcPos, WithPos, }; pub use crate::project::{Project, SourceFile}; diff --git a/vhdl_lang/src/project.rs b/vhdl_lang/src/project.rs index 7ec6bd44..2defcb3e 100644 --- a/vhdl_lang/src/project.rs +++ b/vhdl_lang/src/project.rs @@ -142,10 +142,10 @@ impl Project { self.files.insert( source.file_name().to_owned(), SourceFile { - source, library_names, - parser_diagnostics, + source, design_file, + parser_diagnostics, }, ); } diff --git a/vhdl_lang/src/syntax/component_declaration.rs b/vhdl_lang/src/syntax/component_declaration.rs index 3e3a9e2f..0786ebcc 100644 --- a/vhdl_lang/src/syntax/component_declaration.rs +++ b/vhdl_lang/src/syntax/component_declaration.rs @@ -2,19 +2,19 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::common::error_on_end_identifier_mismatch; use super::common::ParseResult; use super::interface_declaration::{parse_generic_interface_list, parse_port_interface_list}; use super::tokens::{Kind::*, TokenStream}; use crate::ast::{ComponentDeclaration, InterfaceDeclaration}; -use crate::data::{Diagnostic, DiagnosticHandler}; +use crate::data::{Diagnostic, DiagnosticHandler, WithPos}; pub fn parse_optional_generic_list( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, -) -> ParseResult>> { +) -> ParseResult>>> { let mut list = None; loop { let token = stream.peek_expect()?; @@ -22,11 +22,14 @@ pub fn parse_optional_generic_list( Generic => { stream.move_after(&token); let new_list = parse_generic_interface_list(stream, diagnostics)?; - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?; if list.is_some() { diagnostics.push(Diagnostic::error(token, "Duplicate generic clause")); } else { - list = Some(new_list); + list = Some(WithPos { + item: new_list, + pos: token.pos.combine_into(&semi_token), + }); } } _ => break, @@ -39,7 +42,7 @@ pub fn parse_optional_generic_list( pub fn parse_optional_port_list( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, -) -> ParseResult>> { +) -> ParseResult>>> { let mut list = None; loop { let token = stream.peek_expect()?; @@ -47,11 +50,14 @@ pub fn parse_optional_port_list( Port => { stream.move_after(&token); let new_list = parse_port_interface_list(stream, diagnostics)?; - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?; if list.is_some() { diagnostics.push(Diagnostic::error(token, "Duplicate port clause")); } else { - list = Some(new_list); + list = Some(WithPos { + item: new_list, + pos: token.pos.combine_into(&semi_token), + }); } } Generic => { @@ -92,8 +98,8 @@ pub fn parse_component_declaration( Ok(ComponentDeclaration { ident, - generic_list: generic_list.unwrap_or_default(), - port_list: port_list.unwrap_or_default(), + generic_list: generic_list.map_or(Vec::new(), |x| x.item), + port_list: port_list.map_or(Vec::new(), |x| x.item), }) } @@ -102,7 +108,7 @@ mod tests { use super::*; use crate::ast::Ident; - use crate::syntax::test::Code; + use crate::syntax::test::{source_range, Code}; fn to_component( ident: Ident, @@ -222,7 +228,13 @@ end "Duplicate generic clause" )] ); - assert_eq!(result, Ok(Some(vec![code.s1("foo : natural").generic()])),); + assert_eq!( + result, + Ok(Some(WithPos { + item: vec![code.s1("foo : natural").generic()], + pos: source_range(&code, "generic (", ");"), + })), + ); } #[test] @@ -246,7 +258,13 @@ end "Duplicate port clause" )] ); - assert_eq!(result, Ok(Some(vec![code.s1("foo : natural").port()])),); + assert_eq!( + result, + Ok(Some(WithPos { + item: vec![code.s1("foo : natural").port()], + pos: source_range(&code, "port (", ");"), + })), + ); } #[test] @@ -270,6 +288,12 @@ end "Generic clause must come before port clause" )] ); - assert_eq!(result, Ok(Some(vec![code.s1("foo : natural").port()])),); + assert_eq!( + result, + Ok(Some(WithPos { + item: vec![code.s1("foo : natural").port()], + pos: source_range(&code, "port (", ");"), + })), + ); } } diff --git a/vhdl_lang/src/syntax/concurrent_statement.rs b/vhdl_lang/src/syntax/concurrent_statement.rs index 6cea181b..0b101a6a 100644 --- a/vhdl_lang/src/syntax/concurrent_statement.rs +++ b/vhdl_lang/src/syntax/concurrent_statement.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::common::error_on_end_identifier_mismatch; use super::common::ParseResult; @@ -27,6 +27,7 @@ use crate::data::*; /// LRM 11.2 Block statement pub fn parse_block_statement( stream: &mut TokenStream, + start_pos: SrcPos, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { let token = stream.peek_expect()?; @@ -48,12 +49,13 @@ pub fn parse_block_statement( stream.expect_kind(Block)?; // @TODO check name stream.pop_if_kind(Identifier)?; - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?; Ok(BlockStatement { guard_condition, header, decl, statements, + source_range: start_pos.combine_into(&semi_token.pos), }) } @@ -154,6 +156,7 @@ fn parse_block_header( pub fn parse_process_statement( stream: &mut TokenStream, postponed: bool, + start_pos: SrcPos, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { let token = stream.peek_expect()?; @@ -207,12 +210,13 @@ pub fn parse_process_statement( stream.expect_kind(Process)?; // @TODO check name stream.pop_if_kind(Identifier)?; - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?; Ok(ProcessStatement { postponed, sensitivity_list, decl, statements, + source_range: start_pos.combine_into(&semi_token.pos), }) } @@ -557,10 +561,10 @@ pub fn parse_concurrent_statement( try_token_kind!( token, Block => { - ConcurrentStatement::Block(parse_block_statement(stream, diagnostics)?) + ConcurrentStatement::Block(parse_block_statement(stream, token.pos, diagnostics)?) }, Process => { - ConcurrentStatement::Process(parse_process_statement(stream, false, diagnostics)?) + ConcurrentStatement::Process(parse_process_statement(stream, false, token.pos, diagnostics)?) }, Component => { let unit = InstantiatedUnit::Component(parse_selected_name(stream)?); @@ -589,9 +593,10 @@ pub fn parse_concurrent_statement( Case => ConcurrentStatement::CaseGenerate(parse_case_generate_statement(stream, diagnostics)?), Assert => ConcurrentStatement::Assert(parse_concurrent_assert_statement(stream, false)?), Postponed => { + let start_pos = token.pos; let token = stream.expect()?; match token.kind { - Process => ConcurrentStatement::Process(parse_process_statement(stream, true, diagnostics)?), + Process => ConcurrentStatement::Process(parse_process_statement(stream, true, start_pos, diagnostics)?), Assert => ConcurrentStatement::Assert(parse_concurrent_assert_statement(stream, true)?), With => ConcurrentStatement::Assignment(parse_selected_signal_assignment(stream, true)?), _ => { @@ -622,7 +627,9 @@ pub fn parse_concurrent_statement( parse_assignment_known_target(stream, name.map_into(Target::Name))? }, LeftPar => { - let target = parse_aggregate_leftpar_known(stream)?.map_into(Target::Aggregate); + let start = token.pos; + let mut target = parse_aggregate_leftpar_known(stream)?.map_into(Target::Aggregate); + target.pos = start.combine_into(&target.pos); let token = stream.expect()?; parse_assignment_or_procedure_call(stream, &token, target)? } @@ -704,7 +711,7 @@ pub fn parse_labeled_concurrent_statement( mod tests { use super::*; use crate::ast::{Alternative, AssertStatement, DelayMechanism, Selection}; - use crate::syntax::test::Code; + use crate::syntax::test::{source_range, Code}; #[test] fn test_concurrent_procedure() { @@ -799,6 +806,7 @@ end block; label: Some(code.s1("name2").ident()), statement: ConcurrentStatement::ProcedureCall(call), }], + source_range: source_range(&code, "block", "end block;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -824,6 +832,7 @@ end block name; }, decl: vec![], statements: vec![], + source_range: source_range(&code, "block", "end block name;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -849,6 +858,7 @@ end block; }, decl: vec![], statements: vec![], + source_range: source_range(&code, "block", "end block;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -874,6 +884,7 @@ end block; }, decl: vec![], statements: vec![], + source_range: source_range(&code, "block", "end block;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -903,6 +914,7 @@ end block; }, decl: vec![], statements: vec![], + source_range: source_range(&code, "block", "end block;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -923,6 +935,7 @@ end process; sensitivity_list: None, decl: vec![], statements: vec![], + source_range: source_range(&code, "process", "end process;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -943,6 +956,7 @@ end process name; sensitivity_list: None, decl: vec![], statements: vec![], + source_range: source_range(&code, "process", "end process name;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -963,6 +977,7 @@ end process; sensitivity_list: None, decl: vec![], statements: vec![], + source_range: source_range(&code, "postponed", "end process;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -983,6 +998,7 @@ end postponed process; sensitivity_list: None, decl: vec![], statements: vec![], + source_range: source_range(&code, "postponed", "process;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -1004,6 +1020,7 @@ end postponed process; sensitivity_list: None, decl: Vec::new(), statements: Vec::new(), + source_range: source_range(&code, "process", "process;"), }; assert_eq!( diagnostics, @@ -1032,6 +1049,7 @@ end process; ])), decl: vec![], statements: vec![], + source_range: source_range(&code, "process", "end process;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -1053,6 +1071,7 @@ end process; sensitivity_list: Some(SensitivityList::Names(Vec::new())), decl: Vec::new(), statements: Vec::new(), + source_range: source_range(&code, "process", "end process;"), }; assert_eq!( diagnostics, @@ -1084,6 +1103,7 @@ end process; code.s1("foo <= true;").sequential_statement(), code.s1("wait;").sequential_statement(), ], + source_range: source_range(&code, "process", "end process;"), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); diff --git a/vhdl_lang/src/syntax/configuration.rs b/vhdl_lang/src/syntax/configuration.rs index 5e9413d4..bd3dd280 100644 --- a/vhdl_lang/src/syntax/configuration.rs +++ b/vhdl_lang/src/syntax/configuration.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::common::error_on_end_identifier_mismatch; use super::common::ParseResult; @@ -268,7 +268,7 @@ pub fn parse_configuration_declaration( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Configuration)?; + let configuration_token = stream.expect_kind(Configuration)?; let ident = stream.expect_ident()?; stream.expect_kind(Of)?; let entity_name = parse_selected_name(stream)?; @@ -301,7 +301,8 @@ pub fn parse_configuration_declaration( if let Some(diagnostic) = error_on_end_identifier_mismatch(&ident, &end_ident) { diagnostics.push(diagnostic) } - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?; + let source_range = configuration_token.pos.combine_into(&semi_token); Ok(ConfigurationDeclaration { context_clause: ContextClause::default(), ident, @@ -309,6 +310,7 @@ pub fn parse_configuration_declaration( decl, vunit_bind_inds, block_config, + source_range, }) } @@ -351,7 +353,7 @@ pub fn parse_configuration_specification( #[cfg(test)] mod tests { use super::*; - use crate::syntax::test::Code; + use crate::syntax::test::{source_range, Code}; #[test] fn empty_configuration() { @@ -375,7 +377,8 @@ end; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + source_range: source_range(&code, "configuration", "end;") } ); } @@ -402,7 +405,8 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -433,7 +437,8 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -466,7 +471,8 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -493,7 +499,8 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -535,7 +542,8 @@ end configuration cfg; items: vec![], }) ], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -580,7 +588,8 @@ end configuration cfg; items: vec![], }), }),], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -636,7 +645,8 @@ end configuration cfg; items: vec![], }), }),], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -683,7 +693,8 @@ end configuration cfg; vunit_bind_inds: Vec::new(), block_config: None, }),], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } @@ -761,7 +772,8 @@ end configuration cfg; block_config: None, }) ], - } + }, + source_range: source_range(&code, "configuration cfg", "end configuration cfg;"), } ); } diff --git a/vhdl_lang/src/syntax/context.rs b/vhdl_lang/src/syntax/context.rs index b43dbe58..829a9736 100644 --- a/vhdl_lang/src/syntax/context.rs +++ b/vhdl_lang/src/syntax/context.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::common::error_on_end_identifier_mismatch; use super::common::ParseResult; @@ -94,6 +94,7 @@ pub fn parse_context( if stream.skip_if_kind(Is)? { let mut items = Vec::with_capacity(16); let end_ident; + let semi_pos; loop { let token = stream.expect()?; try_token_kind!( @@ -104,7 +105,7 @@ pub fn parse_context( End => { stream.pop_if_kind(Context)?; end_ident = stream.pop_optional_ident()?; - stream.expect_kind(SemiColon)?; + semi_pos = Some(stream.expect_kind(SemiColon)?.pos); break; } ) @@ -113,10 +114,17 @@ pub fn parse_context( let ident = to_simple_name(name)?; diagnostics.push_some(error_on_end_identifier_mismatch(&ident, &end_ident)); - + let source_range = match semi_pos { + Some(semi) => context_token.pos.combine_into(&semi), + None => match stream.peek_expect() { + Ok(token) => context_token.pos.combine_into(&token.pos), + Err(err) => context_token.pos.combine_into(&err.pos), + }, + }; Ok(DeclarationOrReference::Declaration(ContextDeclaration { ident, items, + source_range, })) } else { // Context reference @@ -140,7 +148,7 @@ mod tests { use super::*; use crate::data::Diagnostic; - use crate::syntax::test::Code; + use crate::syntax::test::{source_range, Code}; #[test] fn test_library_clause_single_name() { @@ -252,7 +260,8 @@ end context ident; code.with_stream_no_diagnostics(parse_context), DeclarationOrReference::Declaration(ContextDeclaration { ident: code.s1("ident").ident(), - items: vec![] + items: vec![], + source_range: source_range(&code, "context", ";"), }) ); } @@ -278,7 +287,8 @@ end context ident2; context, DeclarationOrReference::Declaration(ContextDeclaration { ident: code.s1("ident").ident(), - items: vec![] + items: vec![], + source_range: source_range(&code, "context ident", "end context ident2;"), }) ); } @@ -317,7 +327,8 @@ end context; }), code.s1("context foo.ctx;") ), - ] + ], + source_range: source_range(&code, "context ident", "end context;"), }) ) } diff --git a/vhdl_lang/src/syntax/declarative_part.rs b/vhdl_lang/src/syntax/declarative_part.rs index 06e03c57..fcb6dbd8 100644 --- a/vhdl_lang/src/syntax/declarative_part.rs +++ b/vhdl_lang/src/syntax/declarative_part.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::alias_declaration::parse_alias_declaration; use super::attributes::parse_attribute; @@ -16,30 +16,32 @@ use super::subprogram::parse_subprogram; use super::tokens::{Kind::*, *}; use super::type_declaration::parse_type_declaration; use crate::ast::{ContextClause, Declaration, PackageInstantiation}; -use crate::data::DiagnosticHandler; +use crate::data::{DiagnosticHandler, WithPos}; pub fn parse_package_instantiation(stream: &mut TokenStream) -> ParseResult { - stream.expect_kind(Package)?; + let package_token = stream.expect_kind(Package)?; let ident = stream.expect_ident()?; stream.expect_kind(Is)?; stream.expect_kind(New)?; let package_name = parse_selected_name(stream)?; let token = stream.expect()?; - let generic_map = try_token_kind!( + let (generic_map, semi_token) = try_token_kind!( token, Generic => { stream.expect_kind(Map)?; let association_list = parse_association_list(stream)?; - stream.expect_kind(SemiColon)?; - Some(association_list) + let semi_token = stream.expect_kind(SemiColon)?; + (Some(association_list), semi_token) }, - SemiColon => None); + SemiColon => (None, token)); + let source_range = package_token.pos.combine_into(&semi_token); Ok(PackageInstantiation { context_clause: ContextClause::default(), ident, package_name, generic_map, + source_range, }) } @@ -171,12 +173,40 @@ pub fn parse_declarative_part_leave_end_token( Ok(declarations) } +pub fn parse_declarative_part_with_pos_leave_end_token( + stream: &mut TokenStream, + diagnostics: &mut dyn DiagnosticHandler, + start_token: Token, +) -> ParseResult>> { + let decl = parse_declarative_part_leave_end_token(stream, diagnostics)?; + let end_pos = match stream.peek_expect() { + Ok(token) => token.pos, + Err(diag) => diag.pos, + }; + Ok(WithPos { + item: decl, + pos: start_token.pos.combine_into_between(&end_pos), + }) +} + +pub fn parse_declarative_part_with_pos( + stream: &mut TokenStream, + diagnostics: &mut dyn DiagnosticHandler, + start_token: Token, + begin_is_end: bool, +) -> ParseResult>> { + let decl = parse_declarative_part_with_pos_leave_end_token(stream, diagnostics, start_token)?; + let end_token = if begin_is_end { Begin } else { End }; + stream.expect_kind(end_token).log(diagnostics); + Ok(decl) +} + #[cfg(test)] mod tests { use super::*; use crate::ast::{ObjectClass, ObjectDeclaration}; use crate::data::Diagnostic; - use crate::syntax::test::Code; + use crate::syntax::test::{source_range, Code}; #[test] fn package_instantiation() { @@ -191,7 +221,8 @@ package ident is new lib.foo.bar; context_clause: ContextClause::default(), ident: code.s1("ident").ident(), package_name: code.s1("lib.foo.bar").selected_name(), - generic_map: None + generic_map: None, + source_range: source_range(&code, "package", ";"), } ); } @@ -217,7 +248,8 @@ package ident is new lib.foo.bar foo => bar )") .association_list() - ) + ), + source_range: source_range(&code, "package", ");"), } ); } diff --git a/vhdl_lang/src/syntax/design_unit.rs b/vhdl_lang/src/syntax/design_unit.rs index 6b2003e9..657dbd7a 100644 --- a/vhdl_lang/src/syntax/design_unit.rs +++ b/vhdl_lang/src/syntax/design_unit.rs @@ -2,22 +2,21 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com - -use super::tokens::{Kind::*, TokenStream}; - +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::common::error_on_end_identifier_mismatch; use super::common::ParseResult; use super::component_declaration::{parse_optional_generic_list, parse_optional_port_list}; -use super::concurrent_statement::parse_labeled_concurrent_statements; +use super::concurrent_statement::parse_labeled_concurrent_statements_end_token; use super::configuration::parse_configuration_declaration; use super::context::{ parse_context, parse_library_clause, parse_use_clause, DeclarationOrReference, }; use super::declarative_part::{ - parse_declarative_part, parse_declarative_part_leave_end_token, parse_package_instantiation, + parse_declarative_part_leave_end_token, parse_declarative_part_with_pos, + parse_declarative_part_with_pos_leave_end_token, parse_package_instantiation, }; use super::interface_declaration::parse_generic_interface_list; +use super::tokens::{Kind::*, TokenStream}; use crate::ast::*; use crate::data::*; @@ -27,10 +26,10 @@ pub fn parse_entity_declaration( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Entity)?; + let entity_token = stream.expect_kind(Entity)?; let ident = stream.expect_ident()?; - stream.expect_kind(Is)?; + let is_token = stream.expect_kind(Is)?; let generic_clause = parse_optional_generic_list(stream, diagnostics)?; let port_clause = parse_optional_port_list(stream, diagnostics)?; @@ -38,17 +37,37 @@ pub fn parse_entity_declaration( let decl = parse_declarative_part_leave_end_token(stream, diagnostics)?; let token = stream.expect()?; + let decl = { + let decl_start = if let Some(ref ports) = port_clause { + &ports.pos + } else if let Some(ref generics) = generic_clause { + &generics.pos + } else { + &is_token.pos + }; + WithPos { + item: decl, + pos: decl_start.clone().combine_into_between(&token.pos), + } + }; let statements = try_token_kind!( token, - End => Vec::new(), - Begin => parse_labeled_concurrent_statements(stream, diagnostics)? + End => None, + Begin => { + let (items, end_token) = parse_labeled_concurrent_statements_end_token(stream, diagnostics)?; + Some(WithPos{ + item: items, + pos: token.pos.combine_into_between(&end_token), + })} ); stream.pop_if_kind(Entity)?; let end_ident = stream.pop_optional_ident()?; if let Some(diagnostic) = error_on_end_identifier_mismatch(&ident, &end_ident) { diagnostics.push(diagnostic); } - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?; + let source_range = entity_token.pos.combine_into(&semi_token); + Ok(EntityDeclaration { context_clause: ContextClause::default(), ident, @@ -56,6 +75,7 @@ pub fn parse_entity_declaration( port_clause, decl, statements, + source_range, }) } @@ -64,15 +84,22 @@ pub fn parse_architecture_body( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Architecture)?; + let architecture_token = stream.expect_kind(Architecture)?; let ident = stream.expect_ident()?; stream.expect_kind(Of)?; let entity_name = stream.expect_ident()?; - stream.expect_kind(Is)?; - - let decl = parse_declarative_part(stream, diagnostics, true)?; + let is_token = stream.expect_kind(Is)?; + let decl = parse_declarative_part_with_pos_leave_end_token(stream, diagnostics, is_token)?; + let statements_start = match stream.expect_kind(Begin) { + Ok(begin_token) => begin_token.pos, + Err(err) => { + diagnostics.push(err); + decl.pos.clone() + } + }; - let statements = parse_labeled_concurrent_statements(stream, diagnostics)?; + let (statements, end_token) = + parse_labeled_concurrent_statements_end_token(stream, diagnostics)?; stream.pop_if_kind(Architecture)?; let end_ident = stream.pop_optional_ident()?; @@ -80,14 +107,18 @@ pub fn parse_architecture_body( diagnostics.push(diagnostic); } - stream.expect_kind(SemiColon)?; - + let semi_token = stream.expect_kind(SemiColon)?; + let source_range = architecture_token.pos.combine_into(&semi_token); Ok(ArchitectureBody { context_clause: ContextClause::default(), ident, entity_name: entity_name.into_ref(), decl, - statements, + statements: WithPos { + item: statements, + pos: statements_start.combine_into_between(&end_token.pos), + }, + source_range, }) } @@ -96,32 +127,38 @@ pub fn parse_package_declaration( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Package)?; + let package_token = stream.expect_kind(Package)?; let ident = stream.expect_ident()?; - stream.expect_kind(Is)?; + let mut decl_start_token = stream.expect_kind(Is)?; let generic_clause = { - if stream.skip_if_kind(Generic)? { + if let Some(generic_token) = stream.pop_if_kind(Generic)? { let decl = parse_generic_interface_list(stream, diagnostics)?; - stream.expect_kind(SemiColon)?; - Some(decl) + decl_start_token = stream.expect_kind(SemiColon)?; + Some(WithPos { + item: decl, + pos: generic_token.pos.combine_into(&decl_start_token), + }) } else { None } }; - let decl = parse_declarative_part(stream, diagnostics, false)?; + + let decl = parse_declarative_part_with_pos(stream, diagnostics, decl_start_token, false)?; stream.pop_if_kind(Package)?; let end_ident = stream.pop_optional_ident()?; if let Some(diagnostic) = error_on_end_identifier_mismatch(&ident, &end_ident) { diagnostics.push(diagnostic); } stream.pop_if_kind(Identifier)?; - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?; + let source_range = package_token.pos.combine_into(&semi_token); Ok(PackageDeclaration { context_clause: ContextClause::default(), ident, generic_clause, decl, + source_range, }) } @@ -130,12 +167,12 @@ pub fn parse_package_body( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Package)?; + let package_token = stream.expect_kind(Package)?; stream.expect_kind(Body)?; let ident = stream.expect_ident()?; - stream.expect_kind(Is)?; - let decl = parse_declarative_part(stream, diagnostics, false)?; + let is_token = stream.expect_kind(Is)?; + let decl = parse_declarative_part_with_pos(stream, diagnostics, is_token, false)?; if stream.skip_if_kind(Package)? { stream.expect_kind(Body)?; } @@ -143,12 +180,13 @@ pub fn parse_package_body( if let Some(diagnostic) = error_on_end_identifier_mismatch(&ident, &end_ident) { diagnostics.push(diagnostic); } - stream.expect_kind(SemiColon)?; - + let semi_token = stream.expect_kind(SemiColon)?; + let source_range = package_token.pos.combine_into(&semi_token); Ok(PackageBody { context_clause: ContextClause::default(), ident: ident.into_ref(), decl, + source_range, }) } @@ -278,9 +316,11 @@ pub fn parse_design_file( #[cfg(test)] mod tests { use super::*; - use crate::data::Diagnostic; - use crate::syntax::test::{check_diagnostics, check_no_diagnostics, Code}; + use crate::syntax::test::{ + check_diagnostics, check_no_diagnostics, source_range, source_range_between, Code, + }; + use pretty_assertions::assert_eq; fn parse_str(code: &str) -> (Code, DesignFile, Vec) { let code = Code::new(code); @@ -309,14 +349,18 @@ mod tests { } /// An simple entity with only a name - fn simple_entity(ident: Ident) -> AnyDesignUnit { + fn simple_entity(code: &Code, ident: &str) -> AnyDesignUnit { AnyDesignUnit::Primary(AnyPrimaryUnit::Entity(EntityDeclaration { context_clause: ContextClause::default(), - ident, + ident: code.s1(ident).ident(), generic_clause: None, port_clause: None, - decl: vec![], - statements: vec![], + decl: WithPos { + item: vec![], + pos: source_range_between(&code, &format!("entity {} is", &ident), "end"), + }, + statements: None, + source_range: source_range(code, &format!("entity {}", &ident), ";"), })) } @@ -328,10 +372,7 @@ entity myent is end entity; ", ); - assert_eq!( - design_file.design_units, - [simple_entity(code.s1("myent").ident())] - ); + assert_eq!(design_file.design_units, [simple_entity(&code, "myent")]); let (code, design_file) = parse_ok( " @@ -339,10 +380,7 @@ entity myent is end entity myent; ", ); - assert_eq!( - design_file.design_units, - [simple_entity(code.s1("myent").ident())] - ); + assert_eq!(design_file.design_units, [simple_entity(&code, "myent")]); } #[test] @@ -359,10 +397,17 @@ end entity; EntityDeclaration { context_clause: ContextClause::default(), ident: code.s1("myent").ident(), - generic_clause: Some(Vec::new()), + generic_clause: Some(WithPos { + item: Vec::new(), + pos: source_range(&code, "generic (", ");") + }), port_clause: None, - decl: vec![], - statements: vec![], + decl: WithPos { + item: vec![], + pos: source_range_between(&code, ");", "end"), + }, + statements: None, + source_range: source_range(&code, "entity", "end entity;"), } ); } @@ -386,10 +431,17 @@ end entity; item: code.symbol("myent"), pos: code.s1("myent").pos() }, - generic_clause: Some(vec![code.s1("runner_cfg : string").generic()]), + generic_clause: Some(WithPos { + item: vec![code.s1("runner_cfg : string").generic()], + pos: source_range(&code, "generic (", ");"), + }), port_clause: None, - decl: vec![], - statements: vec![], + decl: WithPos { + item: vec![], + pos: source_range_between(&code, ");", "end"), + }, + statements: None, + source_range: source_range(&code, "entity", "end entity;"), } ); } @@ -409,9 +461,16 @@ end entity; context_clause: ContextClause::default(), ident: code.s1("myent").ident(), generic_clause: None, - port_clause: Some(vec![]), - decl: vec![], - statements: vec![], + port_clause: Some(WithPos { + item: vec![], + pos: source_range(&code, "port (", ");"), + }), + decl: WithPos { + item: vec![], + pos: source_range_between(&code, ");", "end"), + }, + statements: None, + source_range: source_range(&code, "entity", "end entity;"), } ); } @@ -432,8 +491,15 @@ end entity; ident: code.s1("myent").ident(), generic_clause: None, port_clause: None, - decl: vec![], - statements: vec![], + decl: WithPos { + item: vec![], + pos: source_range_between(&code, "is", "begin"), + }, + statements: Some(WithPos { + item: vec![], + pos: source_range_between(&code, "begin", "end"), + }), + source_range: source_range(&code, "entity", "end entity;"), } ); } @@ -454,8 +520,12 @@ end entity; ident: code.s1("myent").ident(), generic_clause: None, port_clause: None, - decl: code.s1("constant foo : natural := 0;").declarative_part(), - statements: vec![], + decl: WithPos { + item: code.s1("constant foo : natural := 0;").declarative_part(), + pos: source_range_between(&code, "myent is", "end entity"), + }, + statements: None, + source_range: source_range(&code, "entity", "end entity;"), } ); } @@ -477,8 +547,15 @@ end entity; ident: code.s1("myent").ident(), generic_clause: None, port_clause: None, - decl: vec![], - statements: vec![code.s1("check(clk, valid);").concurrent_statement()], + decl: WithPos { + item: vec![], + pos: source_range_between(&code, "is", "begin"), + }, + statements: Some(WithPos { + item: vec![code.s1("check(clk, valid);").concurrent_statement()], + pos: source_range_between(&code, "begin", "end") + }), + source_range: source_range(&code, "entity", "end entity;"), } ); } @@ -503,22 +580,29 @@ end; assert_eq!( design_file.design_units, [ - simple_entity(code.s1("myent").ident()), - simple_entity(code.s1("myent2").ident()), - simple_entity(code.s1("myent3").ident()), - simple_entity(code.s1("myent4").ident()) + simple_entity(&code, "myent"), + simple_entity(&code, "myent2"), + simple_entity(&code, "myent3"), + simple_entity(&code, "myent4") ] ); } // An simple entity with only a name - fn simple_architecture(ident: Ident, entity_name: Ident) -> AnyDesignUnit { + fn simple_architecture(code: &Code, ident: &str, entity_name: &str) -> AnyDesignUnit { AnyDesignUnit::Secondary(AnySecondaryUnit::Architecture(ArchitectureBody { context_clause: ContextClause::default(), - ident, - entity_name: entity_name.into_ref(), - decl: Vec::new(), - statements: vec![], + ident: code.s1(ident).ident(), + entity_name: code.s1(entity_name).ident().into_ref(), + decl: WithPos { + item: Vec::new(), + pos: source_range_between(code, "is", "begin"), + }, + statements: WithPos { + item: vec![], + pos: source_range_between(code, "begin", "end"), + }, + source_range: source_range(code, &format!("architecture {}", &ident), ";"), })) } @@ -533,10 +617,7 @@ end architecture; ); assert_eq!( design_file.design_units, - [simple_architecture( - code.s1("arch_name").ident(), - code.s1("myent").ident() - )] + [simple_architecture(&code, "arch_name", "myent")] ); } @@ -551,10 +632,7 @@ end architecture arch_name; ); assert_eq!( design_file.design_units, - [simple_architecture( - code.s1("arch_name").ident(), - code.s1("myent").ident() - )] + [simple_architecture(&code, "arch_name", "myent")] ); } @@ -569,10 +647,7 @@ end; ); assert_eq!( design_file.design_units, - [simple_architecture( - code.s1("arch_name").ident(), - code.s1("myent").ident() - )] + [simple_architecture(&code, "arch_name", "myent")] ); } @@ -590,7 +665,11 @@ end package; context_clause: ContextClause::default(), ident: code.s1("pkg_name").ident(), generic_clause: None, - decl: vec![], + decl: WithPos { + item: vec![], + pos: source_range_between(&code, "is", "end package;"), + }, + source_range: source_range(&code, "package", "end package;"), } ); } @@ -611,12 +690,16 @@ end package; context_clause: ContextClause::default(), ident: code.s1("pkg_name").ident(), generic_clause: None, - decl: code - .s1("\ + decl: WithPos { + item: code + .s1("\ type foo; constant bar : natural := 0; ") - .declarative_part(), + .declarative_part(), + pos: source_range_between(&code, "is", "end package;") + }, + source_range: source_range(&code, "package", "end package;"), } ); } @@ -638,11 +721,15 @@ end package; PackageDeclaration { context_clause: ContextClause::default(), ident: code.s1("pkg_name").ident(), - generic_clause: Some(vec![ - code.s1("type foo").generic(), - code.s1("type bar").generic() - ]), - decl: vec![] + generic_clause: Some(WithPos { + item: vec![code.s1("type foo").generic(), code.s1("type bar").generic()], + pos: source_range(&code, "generic (", ");"), + }), + decl: WithPos { + item: vec![], + pos: source_range_between(&code, ");", "end package;"), + }, + source_range: source_range(&code, "package", "end package;"), } ); } @@ -674,8 +761,12 @@ end entity; ident: code.s1("myent").ident(), generic_clause: None, port_clause: None, - decl: vec![], - statements: vec![], + decl: WithPos { + item: vec![], + pos: source_range_between(&code, "is", "end"), + }, + statements: None, + source_range: source_range(&code, "entity", "end entity;"), } ))] } diff --git a/vhdl_lang/src/syntax/test.rs b/vhdl_lang/src/syntax/test.rs index 78ad2bf7..95f35d6d 100644 --- a/vhdl_lang/src/syntax/test.rs +++ b/vhdl_lang/src/syntax/test.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use super::alias_declaration::parse_alias_declaration; use super::common::ParseResult; @@ -597,6 +597,48 @@ impl AsRef for Code { } } +// Create a Range spanning from the first substring occurance of start to +// to the first occurance of end after start (start and end may overlap) +pub fn source_range(code: &Code, start: &str, end: &str) -> SrcPos { + let start = code.s1(start).pos; + let mut end_occurance = 1; + loop { + let end = code.s(end, end_occurance).pos; + if end.range().start.line >= start.range().start.line + && end.range().end.line >= start.range().end.line + { + break start.combine_into(&end); + } + end_occurance += 1; + if end_occurance > 1000 { + panic!("Unable to find range"); + } + } +} + +pub fn source_range_between(code: &Code, start: &str, end: &str) -> SrcPos { + let start = code.s1(start).pos; + let mut end_occurance = 1; + loop { + let end = code.s(end, end_occurance).pos; + if end.range().start.line >= start.range().start.line + && end.range().end.line >= start.range().end.line + { + break SrcPos::new( + code.source().clone(), + Range { + start: start.pos().end(), + end: end.pos().start(), + }, + ); + } + end_occurance += 1; + if end_occurance > 1000 { + panic!("Unable to find range"); + } + } +} + mod tests { use super::*; diff --git a/vhdl_ls/src/lib.rs b/vhdl_ls/src/lib.rs index 8673dd77..6e13dc92 100644 --- a/vhdl_ls/src/lib.rs +++ b/vhdl_ls/src/lib.rs @@ -2,12 +2,13 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com #![allow(clippy::upper_case_acronyms)] #[macro_use] extern crate log; +mod requests; mod rpc_channel; mod stdio_server; mod vhdl_server; diff --git a/vhdl_ls/src/requests.rs b/vhdl_ls/src/requests.rs new file mode 100644 index 00000000..766efd56 --- /dev/null +++ b/vhdl_ls/src/requests.rs @@ -0,0 +1,7 @@ +// 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) 2021, Olof Kraigher olof.kraigher@gmail.com + +pub mod document_symbol; diff --git a/vhdl_ls/src/requests/document_symbol.rs b/vhdl_ls/src/requests/document_symbol.rs new file mode 100644 index 00000000..26975858 --- /dev/null +++ b/vhdl_ls/src/requests/document_symbol.rs @@ -0,0 +1,2110 @@ +// 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) 2021, Olof Kraigher olof.kraigher@gmail.com + +use lsp_types::{DocumentSymbol, DocumentSymbolResponse, SymbolKind, Url}; +use vhdl_lang::ast::*; +use vhdl_lang::{Source, SrcPos, VHDLParser, WithPos}; + +pub fn nested_document_symbol_response_from_file(uri: &Url) -> Option { + match uri.to_file_path() { + Ok(path) => { + let mut diagnostics = vec![]; + match VHDLParser::default().parse_design_file(&path, &mut diagnostics) { + Ok((_, design_file)) => Some(nested_document_symbol_response(&design_file)), + Err(_) => None, + } + } + _ => None, + } +} + +pub fn nested_document_symbol_response_from_source(source: &Source) -> DocumentSymbolResponse { + let mut diagnostics = vec![]; + let design_file = VHDLParser::default().parse_design_source(&source, &mut diagnostics); + nested_document_symbol_response(&design_file) +} + +pub fn nested_document_symbol_response(design_file: &DesignFile) -> DocumentSymbolResponse { + let mut response = vec![]; + for design_unit in design_file.design_units.iter() { + response.push(design_unit.document_symbol()); + } + DocumentSymbolResponse::from(response) +} + +pub trait HasDocumentSymbol { + fn document_symbol(&self) -> DocumentSymbol; +} + +impl HasDocumentSymbol for AnyDesignUnit { + fn document_symbol(&self) -> DocumentSymbol { + match self { + AnyDesignUnit::Primary(ref unit) => unit.document_symbol(), + AnyDesignUnit::Secondary(ref unit) => unit.document_symbol(), + } + } +} + +impl HasDocumentSymbol for AnyPrimaryUnit { + fn document_symbol(&self) -> DocumentSymbol { + match self { + AnyPrimaryUnit::Entity(ref unit) => unit.document_symbol(), + AnyPrimaryUnit::Configuration(ref unit) => unit.document_symbol(), + AnyPrimaryUnit::Package(ref unit) => unit.document_symbol(), + AnyPrimaryUnit::PackageInstance(ref unit) => unit.document_symbol(), + AnyPrimaryUnit::Context(ref unit) => unit.document_symbol(), + } + } +} + +impl HasDocumentSymbol for EntityDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(&self.context_clause, &mut children); + push_optional_interface_list( + self.generic_clause.as_ref(), + InterfaceListType::Generic, + &mut children, + ); + push_optional_interface_list( + self.port_clause.as_ref(), + InterfaceListType::Port, + &mut children, + ); + push_declarations(&self.decl, &mut children); + if let Some(ref statements) = self.statements { + push_concurrent_statements(statements, &mut children); + } + let mut range = to_lsp_range(&self.source_range); + if !self.context_clause.is_empty() { + range.start = to_lsp_pos(self.context_clause[0].pos.start()); + } + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("entity")), + kind: SymbolKind::Interface, + deprecated: None, + range, + selection_range: to_lsp_range(&self.ident.pos), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for ConfigurationDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(&self.context_clause, &mut children); + let mut range = to_lsp_range(&self.source_range); + if !self.context_clause.is_empty() { + range.start = to_lsp_pos(self.context_clause[0].pos.start()); + } + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("configuration")), + kind: SymbolKind::Constructor, + deprecated: None, + range, + selection_range: to_lsp_range(&self.ident.pos), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for PackageDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(&self.context_clause, &mut children); + push_optional_interface_list( + self.generic_clause.as_ref(), + InterfaceListType::Generic, + &mut children, + ); + push_declarations(&self.decl, &mut children); + let mut range = to_lsp_range(&self.source_range); + if !self.context_clause.is_empty() { + range.start = to_lsp_pos(self.context_clause[0].pos.start()); + } + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("package")), + kind: SymbolKind::Package, + deprecated: None, + range, + selection_range: to_lsp_range(&self.ident.pos), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for PackageInstantiation { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(&self.context_clause, &mut children); + let mut range = to_lsp_range(&self.source_range); + if !self.context_clause.is_empty() { + range.start = to_lsp_pos(self.context_clause[0].pos.start()); + } + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("package instance")), + kind: SymbolKind::Package, + deprecated: None, + range, + selection_range: to_lsp_range(&self.ident.pos), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for ContextDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(&self.items, &mut children); + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("context")), + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&self.source_range), + selection_range: to_lsp_range(&self.ident.pos), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for AnySecondaryUnit { + fn document_symbol(&self) -> DocumentSymbol { + match self { + AnySecondaryUnit::PackageBody(ref unit) => unit.document_symbol(), + AnySecondaryUnit::Architecture(ref unit) => unit.document_symbol(), + } + } +} + +impl HasDocumentSymbol for PackageBody { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(&self.context_clause, &mut children); + push_declarations(&self.decl, &mut children); + let mut range = to_lsp_range(&self.source_range); + if !self.context_clause.is_empty() { + range.start = to_lsp_pos(self.context_clause[0].pos.start()); + } + DocumentSymbol { + name: self.ident.item.item.name_utf8(), + detail: Some(String::from("package body")), + kind: SymbolKind::Package, + deprecated: None, + range, + selection_range: to_lsp_range(&self.ident.item.pos), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for ArchitectureBody { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + let mut range = to_lsp_range(&self.source_range); + if !self.context_clause.is_empty() { + range.start = to_lsp_pos(self.context_clause[0].pos.start()); + } + push_context_clause(&self.context_clause, &mut children); + push_declarations(&self.decl, &mut children); + push_concurrent_statements(&self.statements, &mut children); + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(format!( + "architecture of {}", + self.entity_name.item.item.name().to_string() + )), + kind: SymbolKind::Class, + deprecated: None, + range, + selection_range: to_lsp_range(&self.ident.pos), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for InterfaceDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + match self { + InterfaceDeclaration::Object(ref object) => object.document_symbol(), + InterfaceDeclaration::File(ref file) => file.document_symbol(), + InterfaceDeclaration::Type(ref ident) => DocumentSymbol { + name: ident.item.name_utf8(), + detail: Some(String::from("Type")), + kind: SymbolKind::TypeParameter, + deprecated: None, + range: to_lsp_range(&ident.pos), + selection_range: to_lsp_range(&ident.pos), + children: None, + }, + InterfaceDeclaration::Subprogram(ref subprogram_declaration, _) => { + subprogram_declaration.document_symbol() + } + InterfaceDeclaration::Package(ref package) => package.document_symbol(), + } + } +} + +impl HasDocumentSymbol for InterfaceObjectDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mode = if self.class == ObjectClass::Constant { + String::from("") + } else { + self.mode.to_string() + }; + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(format!("{} {}", mode, self.subtype_indication.to_string())), + kind: symbol_kind_from_object_class(self.class), + deprecated: None, + range: to_lsp_range(&self.ident.pos), + selection_range: to_lsp_range(&self.ident.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for InterfaceFileDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(self.subtype_indication.to_string()), + kind: SymbolKind::File, + deprecated: None, + range: to_lsp_range(&self.ident.pos), + selection_range: to_lsp_range(&self.ident.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for SubprogramDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + match self { + SubprogramDeclaration::Procedure(procedure) => DocumentSymbol { + name: match procedure.designator.item { + SubprogramDesignator::Identifier(ref symbol) => symbol.name_utf8(), + SubprogramDesignator::OperatorSymbol(ref latin1string) => { + format!("\"{}\"", latin1string.to_string()) + } + }, + detail: Some(String::from("procedure")), + kind: SymbolKind::Method, + deprecated: None, + range: to_lsp_range(&procedure.designator.pos), + selection_range: to_lsp_range(&procedure.designator.pos), + children: None, + }, + SubprogramDeclaration::Function(function) => DocumentSymbol { + name: match function.designator.item { + SubprogramDesignator::Identifier(ref symbol) => symbol.name_utf8(), + SubprogramDesignator::OperatorSymbol(ref latin1string) => { + format!("\"{}\"", latin1string.to_string()) + } + }, + detail: Some(String::from("function")), + kind: SymbolKind::Function, + deprecated: None, + range: to_lsp_range(&function.designator.pos), + selection_range: to_lsp_range(&function.designator.pos), + children: None, + }, + } + } +} + +impl HasDocumentSymbol for WithPos { + fn document_symbol(&self) -> DocumentSymbol { + if self.item.name_list.len() == 1 { + DocumentSymbol { + name: self.item.name_list[0].to_string(), + detail: Some(String::from("use")), + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&self.pos), + selection_range: to_lsp_range(&self.item.name_list[0].pos), + children: None, + } + } else { + DocumentSymbol { + name: String::from("use"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&self.pos), + selection_range: lsp_types::Range { + start: to_lsp_pos(self.pos.start()), + end: to_lsp_pos(self.pos.start()), + }, + children: none_if_empty( + self.item + .name_list + .iter() + .map(|x| DocumentSymbol { + name: x.to_string(), + detail: Some(String::from("use")), + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&x.pos), + selection_range: to_lsp_range(&x.pos), + children: None, + }) + .collect(), + ), + } + } + } +} + +impl HasDocumentSymbol for InterfacePackageDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("Package")), + kind: SymbolKind::Package, + deprecated: None, + range: to_lsp_range(&self.ident.pos), + selection_range: to_lsp_range(&self.ident.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for Declaration { + fn document_symbol(&self) -> DocumentSymbol { + match self { + Declaration::Object(object) => object.document_symbol(), + Declaration::File(file) => file.document_symbol(), + Declaration::Type(type_decl) => type_decl.document_symbol(), + Declaration::Component(component) => component.document_symbol(), + Declaration::Attribute(attribute) => attribute.document_symbol(), + Declaration::Alias(alias) => alias.document_symbol(), + Declaration::SubprogramDeclaration(subprogram) => subprogram.document_symbol(), + Declaration::SubprogramBody(subprogram) => subprogram.specification.document_symbol(), + Declaration::Use(use_decl) => use_decl.document_symbol(), + Declaration::Package(package) => package.document_symbol(), + Declaration::Configuration(configuration) => configuration.document_symbol(), + } + } +} + +impl HasDocumentSymbol for ObjectDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: None, + kind: symbol_kind_from_object_class(self.class), + deprecated: None, + range: to_lsp_range(&self.ident.pos), + selection_range: to_lsp_range(&self.ident.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for FileDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: None, + kind: SymbolKind::File, + deprecated: None, + range: to_lsp_range(&self.ident.pos), + selection_range: to_lsp_range(&self.ident.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for TypeDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("type")), + kind: symbol_kind_from_type_definition(&self.def), + deprecated: None, + range: to_lsp_range(&self.ident.pos), + selection_range: to_lsp_range(&self.ident.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for ComponentDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("component")), + kind: SymbolKind::Interface, + deprecated: None, + range: to_lsp_range(&self.ident.pos), + selection_range: to_lsp_range(&self.ident.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for Attribute { + fn document_symbol(&self) -> DocumentSymbol { + match self { + Attribute::Specification(spec) => DocumentSymbol { + name: spec.ident.item.name_utf8(), + detail: Some(String::from("attribute")), + kind: SymbolKind::Property, + deprecated: None, + range: to_lsp_range(&spec.ident.pos), + selection_range: to_lsp_range(&spec.ident.pos), + children: None, + }, + Attribute::Declaration(decl) => DocumentSymbol { + name: decl.ident.item.name_utf8(), + detail: Some(String::from("attribute")), + kind: SymbolKind::Property, + deprecated: None, + range: to_lsp_range(&decl.ident.pos), + selection_range: to_lsp_range(&decl.ident.pos), + children: None, + }, + } + } +} + +impl HasDocumentSymbol for AliasDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.designator.to_string(), + detail: Some(String::from("alias")), + kind: SymbolKind::TypeParameter, + deprecated: None, + range: to_lsp_range(&self.designator.pos), + selection_range: to_lsp_range(&self.designator.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for ConfigurationSpecification { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("configuration"), + detail: None, + kind: SymbolKind::Constructor, + deprecated: None, + range: to_lsp_range(&self.spec.component_name.pos), + selection_range: to_lsp_range(&self.spec.component_name.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for LabeledConcurrentStatement { + fn document_symbol(&self) -> DocumentSymbol { + let mut symbol = self.statement.document_symbol(); + if let Some(ref label) = self.label { + symbol.name = label.item.name_utf8(); + symbol.range = lsp_types::Range::new(to_lsp_pos(label.pos.start()), symbol.range.end); + symbol.selection_range = to_lsp_range(&label.pos); + } + symbol + } +} + +impl HasDocumentSymbol for ConcurrentStatement { + fn document_symbol(&self) -> DocumentSymbol { + match &self { + ConcurrentStatement::ProcedureCall(stmt) => stmt.document_symbol(), + ConcurrentStatement::Block(stmt) => stmt.document_symbol(), + ConcurrentStatement::Process(stmt) => stmt.document_symbol(), + ConcurrentStatement::Assert(stmt) => stmt.document_symbol(), + ConcurrentStatement::Assignment(stmt) => stmt.document_symbol(), + ConcurrentStatement::Instance(stmt) => stmt.document_symbol(), + ConcurrentStatement::ForGenerate(stmt) => stmt.document_symbol(), + ConcurrentStatement::IfGenerate(stmt) => stmt.document_symbol(), + ConcurrentStatement::CaseGenerate(stmt) => stmt.document_symbol(), + } + } +} + +impl HasDocumentSymbol for ConcurrentProcedureCall { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.call.name.to_string(), + detail: Some(String::from("procedure call")), + kind: SymbolKind::Method, + deprecated: None, + range: to_lsp_range(&self.call.name.pos), + selection_range: to_lsp_range(&self.call.name.pos), + children: None, + } + } +} + +// TODO: Add children from generics, ports, declarations and statements +impl HasDocumentSymbol for BlockStatement { + fn document_symbol(&self) -> DocumentSymbol { + let block_start = to_lsp_pos(self.source_range.start()); + DocumentSymbol { + name: String::from(" "), + detail: Some(String::from("block")), + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&self.source_range), + selection_range: lsp_types::Range { + start: block_start, + end: block_start, + }, + children: None, + } + } +} + +impl HasDocumentSymbol for ProcessStatement { + fn document_symbol(&self) -> DocumentSymbol { + let start_pos = to_lsp_pos(self.source_range.start()); + DocumentSymbol { + name: String::from(" "), + detail: Some(String::from("process")), + kind: SymbolKind::Event, + deprecated: None, + range: to_lsp_range(&self.source_range), + selection_range: lsp_types::Range { + start: start_pos, + end: start_pos, + }, + children: None, + } + } +} + +impl HasDocumentSymbol for ConcurrentAssertStatement { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from(" "), + detail: Some(String::from("assertion")), + kind: SymbolKind::Field, + deprecated: None, + range: to_lsp_range(&self.statement.condition.pos), + selection_range: to_lsp_range(&self.statement.condition.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for ConcurrentSignalAssignment { + fn document_symbol(&self) -> DocumentSymbol { + let name = match &self.target.item { + Target::Name(name) => name.to_string(), + Target::Aggregate(aggregate) => aggregate + .iter() + .map(|x| match x { + ElementAssociation::Positional(pos) => pos.item.to_string(), + ElementAssociation::Named(_, pos) => pos.item.to_string(), + }) + .collect::>() + .join(", "), + }; + DocumentSymbol { + name, + detail: Some(String::from("assignment")), + kind: SymbolKind::Field, + deprecated: None, + range: to_lsp_range(&self.target.pos), + selection_range: to_lsp_range(&self.target.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for InstantiationStatement { + fn document_symbol(&self) -> DocumentSymbol { + let name = String::from("instantiation"); + match &self.unit { + InstantiatedUnit::Component(component_name) => DocumentSymbol { + name, + detail: Some(component_name.item.to_string()), + kind: SymbolKind::Object, + deprecated: None, + range: to_lsp_range(&component_name.pos), + selection_range: to_lsp_range(&component_name.pos), + children: None, + }, + InstantiatedUnit::Entity(entity_name, architecture_name) => DocumentSymbol { + name, + detail: Some(format!( + "{}{}", + entity_name.item, + architecture_name + .as_ref() + .map_or(String::from(""), |arch| format!("({})", arch)) + )), + kind: SymbolKind::Object, + deprecated: None, + range: to_lsp_range(&entity_name.pos), + selection_range: to_lsp_range(&entity_name.pos), + children: None, + }, + InstantiatedUnit::Configuration(configuration_name) => DocumentSymbol { + name, + detail: Some(configuration_name.item.to_string()), + kind: SymbolKind::Object, + deprecated: None, + range: to_lsp_range(&configuration_name.pos), + selection_range: to_lsp_range(&configuration_name.pos), + children: None, + }, + } + } +} + +impl HasDocumentSymbol for ForGenerateStatement { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from(" "), + detail: Some(String::from("for generate")), + kind: SymbolKind::Field, + deprecated: None, + range: to_lsp_range(&self.index_name.pos), + selection_range: to_lsp_range(&self.index_name.pos), + children: None, + } + } +} + +impl HasDocumentSymbol for IfGenerateStatement { + fn document_symbol(&self) -> DocumentSymbol { + let range = if let Some(cond) = self.conditionals.first() { + to_lsp_range(&cond.condition.pos) + } else { + lsp_types::Range { + start: lsp_types::Position { + line: 0, + character: 0, + }, + end: lsp_types::Position { + line: 0, + character: 0, + }, + } + }; + DocumentSymbol { + name: String::from(" "), + detail: Some(String::from("if generate")), + kind: SymbolKind::Field, + deprecated: None, + range, + selection_range: range, + children: None, + } + } +} + +impl HasDocumentSymbol for CaseGenerateStatement { + fn document_symbol(&self) -> DocumentSymbol { + let range = to_lsp_range(&self.expression.pos); + DocumentSymbol { + name: String::from(" "), + detail: Some(String::from("case generate")), + kind: SymbolKind::Field, + deprecated: None, + range, + selection_range: range, + children: None, + } + } +} + +fn push_interface_list( + list: &WithPos>, + list_type: InterfaceListType, + symbols: &mut Vec, +) { + if !list.item.is_empty() { + let (name, kind) = match list_type { + InterfaceListType::Port => (String::from("ports"), SymbolKind::Interface), + InterfaceListType::Generic => (String::from("generics"), SymbolKind::Constant), + InterfaceListType::Parameter => (String::from("parameters"), SymbolKind::Interface), + }; + symbols.push(DocumentSymbol { + name, + detail: None, + kind, + deprecated: None, + range: to_lsp_range(&list.pos), + selection_range: lsp_types::Range { + start: to_lsp_pos(list.pos.start()), + end: to_lsp_pos(list.pos.start()), + }, + children: none_if_empty(list.item.iter().map(|x| x.document_symbol()).collect()), + }); + } +} + +fn push_optional_interface_list( + list: Option<&WithPos>>, + list_type: InterfaceListType, + symbols: &mut Vec, +) { + if let Some(list) = list { + push_interface_list(list, list_type, symbols); + } +} + +fn push_declarations(list: &WithPos>, symbols: &mut Vec) { + if !list.item.is_empty() { + let range = to_lsp_range(&list.pos); + symbols.push(DocumentSymbol { + name: String::from("declarations"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range, + selection_range: lsp_types::Range { + start: range.start, + end: range.start, + }, + children: Some( + list.item + .iter() + .map(|decl| decl.document_symbol()) + .collect(), + ), + }); + } +} + +fn push_concurrent_statements( + statements: &WithPos>, + symbols: &mut Vec, +) { + let range = to_lsp_range(&statements.pos); + symbols.push(DocumentSymbol { + name: String::from("statements"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range, + selection_range: lsp_types::Range { + start: range.start, + end: range.start, + }, + children: Some( + statements + .item + .iter() + .map(|stmt| stmt.document_symbol()) + .collect(), + ), + }); +} + +fn symbol_kind_from_type_definition(type_definition: &TypeDefinition) -> SymbolKind { + match type_definition { + TypeDefinition::Enumeration(_) => SymbolKind::Enum, + TypeDefinition::Integer(_) => SymbolKind::TypeParameter, + TypeDefinition::Physical(_) => SymbolKind::TypeParameter, + TypeDefinition::Array(_, _) => SymbolKind::Array, + TypeDefinition::Record(_) => SymbolKind::Struct, + TypeDefinition::Access(_) => SymbolKind::Key, + TypeDefinition::Incomplete(_) => SymbolKind::TypeParameter, + TypeDefinition::File(_) => SymbolKind::File, + TypeDefinition::Protected(_) => SymbolKind::Object, + TypeDefinition::ProtectedBody(_) => SymbolKind::Object, + TypeDefinition::Subtype(_) => SymbolKind::TypeParameter, + } +} + +fn to_lsp_pos(position: vhdl_lang::Position) -> lsp_types::Position { + lsp_types::Position { + line: position.line as u64, + character: position.character as u64, + } +} + +fn to_lsp_range(src_pos: &SrcPos) -> lsp_types::Range { + let range = src_pos.range(); + lsp_types::Range { + start: to_lsp_pos(range.start), + end: to_lsp_pos(range.end), + } +} + +fn symbol_kind_from_object_class(object_class: ObjectClass) -> SymbolKind { + match object_class { + ObjectClass::Signal => SymbolKind::Field, + ObjectClass::Constant => SymbolKind::Constant, + ObjectClass::Variable => SymbolKind::Variable, + ObjectClass::SharedVariable => SymbolKind::Variable, + } +} + +fn none_if_empty(vec: Vec) -> Option> { + if vec.is_empty() { + None + } else { + Some(vec) + } +} + +fn push_context_clause(context_clause: &[WithPos], symbols: &mut Vec) { + if let Some(ref first) = context_clause.first() { + let range = first + .pos + .clone() + .combine_into(context_clause.last().unwrap()); + let mut children = vec![]; + for context_item in context_clause.iter() { + match &context_item.item { + ContextItem::Use(use_clause) => { + for name in use_clause.name_list.iter() { + children.push(DocumentSymbol { + name: name.to_string(), + detail: Some(String::from("use")), + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&name.pos), + selection_range: to_lsp_range(&name.pos), + children: None, + }); + } + } + ContextItem::Library(library_clause) => { + for name in library_clause.name_list.iter() { + children.push(DocumentSymbol { + name: name.to_string(), + detail: Some(String::from("library")), + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&name.pos), + selection_range: to_lsp_range(&name.pos), + children: None, + }); + } + } + ContextItem::Context(context_reference) => { + for name in context_reference.name_list.iter() { + children.push(DocumentSymbol { + name: name.to_string(), + detail: Some(String::from("context")), + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&name.pos), + selection_range: to_lsp_range(&name.pos), + children: None, + }); + } + } + }; + } + symbols.push(DocumentSymbol { + name: String::from("context"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(&range), + selection_range: to_lsp_range(&first.pos), + children: none_if_empty(children), + }); + } +} + +#[cfg(test)] +mod tests { + use super::*; + use lsp_types; + use pretty_assertions::assert_eq; + use std::convert::TryInto; + use std::io::Write; + use std::path::Path; + use tempfile::NamedTempFile; + use vhdl_lang::Source; + + fn parse_str(code: &str) -> DesignFile { + let mut diagnostics = vec![]; + let source = Source::inline(Path::new("mockpath"), code); + let parser = VHDLParser::default(); + let design_file = parser.parse_design_source(&source, &mut diagnostics); + for err in diagnostics.iter() { + println!("{}", err.show()); + } + if !diagnostics.is_empty() { + panic!("Found errors"); + } + design_file + } + + fn write_source_file(code: &str) -> (Url, NamedTempFile) { + let mut file = NamedTempFile::new().unwrap(); + file.write_all(code.as_bytes()).unwrap(); + ( + Url::from_file_path(file.path().canonicalize().unwrap()).unwrap(), + file, + ) + } + + fn range(code: &str, start: &str, end: &str) -> lsp_types::Range { + find_range(code, start, 1, end, true) + } + + fn range_between(code: &str, start: &str, end: &str) -> lsp_types::Range { + find_range(code, start, 1, end, false) + } + + fn find_range( + code: &str, + start: &str, + start_occurance: usize, + end: &str, + inclusive: bool, + ) -> lsp_types::Range { + let mut start_line = 0; + let mut start_column = 0; + let mut found_start = false; + let mut end_line = 0; + let mut end_column = 0; + let mut occurance = 0; + for (line_number, line) in code.lines().enumerate() { + if !found_start { + if let Some(pos) = line.find(start) { + occurance += 1; + if occurance == start_occurance { + start_column = pos; + if !inclusive { + start_column += start.len(); + } + start_line = line_number; + found_start = true; + } + } + } + if found_start { + if let Some(pos) = line.find(end) { + end_column = pos; + if inclusive { + end_column += end.len(); + } + end_line = line_number; + break; + } + } + } + + lsp_types::Range { + start: lsp_types::Position { + line: start_line.try_into().unwrap(), + character: start_column.try_into().unwrap(), + }, + end: lsp_types::Position { + line: end_line.try_into().unwrap(), + character: end_column.try_into().unwrap(), + }, + } + } + + fn range1(code: &str, start: &str) -> lsp_types::Range { + range(code, start, start) + } + + fn ieee_context(code: &str) -> DocumentSymbol { + DocumentSymbol { + name: String::from("context"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: range(code, "library ieee", "ieee;"), + selection_range: range(code, "library ieee", "ieee;"), + children: Some(vec![DocumentSymbol { + name: String::from("ieee"), + detail: Some(String::from("library")), + kind: SymbolKind::Namespace, + deprecated: None, + range: range1(code, "ieee"), + selection_range: range1(code, "ieee"), + children: None, + }]), + } + } + + fn simple_generic(code: &str) -> DocumentSymbol { + DocumentSymbol { + name: String::from("generics"), + detail: None, + kind: SymbolKind::Constant, + deprecated: None, + range: range(code, "generic(", ");"), + selection_range: lsp_types::Range { + start: range1(code, "generic").start, + end: range1(code, "generic").start, + }, + children: Some(vec![DocumentSymbol { + name: String::from("g1"), + detail: Some(String::from(" integer")), + kind: SymbolKind::Constant, + deprecated: None, + range: range1(code, "g1"), + selection_range: range1(code, "g1"), + children: None, + }]), + } + } + + fn simple_port(code: &str) -> DocumentSymbol { + DocumentSymbol { + name: String::from("ports"), + detail: None, + kind: SymbolKind::Interface, + deprecated: None, + range: range(code, "port(", ");"), + selection_range: lsp_types::Range { + start: range1(code, "port").start, + end: range1(code, "port").start, + }, + children: Some(vec![DocumentSymbol { + name: String::from("p1"), + detail: Some(String::from("in integer")), + kind: SymbolKind::Field, + deprecated: None, + range: range1(code, "p1"), + selection_range: range1(code, "p1"), + children: None, + }]), + } + } + + fn simple_declaration( + code: &str, + start: &str, + start_occurance: usize, + begin_is_end: bool, + ) -> DocumentSymbol { + let end = if begin_is_end { "begin" } else { "end" }; + let range = find_range(code, start, start_occurance, end, false); + DocumentSymbol { + name: String::from("declarations"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range, + selection_range: lsp_types::Range { + start: range.start, + end: range.start, + }, + children: Some(vec![DocumentSymbol { + name: String::from("decl1"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: range1(code, "decl1"), + selection_range: range1(code, "decl1"), + children: None, + }]), + } + } + + fn simple_statement(code: &str, starts_with_begin: bool) -> DocumentSymbol { + let start = if starts_with_begin { "begin" } else { "end" }; + let stmt_range = range_between(code, start, "end"); + DocumentSymbol { + name: String::from("statements"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: stmt_range, + selection_range: lsp_types::Range { + start: stmt_range.start, + end: stmt_range.start, + }, + children: Some(vec![DocumentSymbol { + name: String::from("stmt1"), + detail: Some(String::from("assignment")), + kind: SymbolKind::Field, + deprecated: None, + range: range(code, "stmt1", "decl1"), + selection_range: range1(code, "stmt1"), + children: None, + }]), + } + } + + #[test] + fn entity_declaration() { + let code = " +library ieee; + +entity ent1 is + generic( + g1 : integer := 3 + ); + port( + p1 : integer + ); + signal decl1 : integer; +begin + stmt1: decl1 <= 1; +end; +"; + let design_file = parse_str(code); + let unit = design_file.design_units.first().unwrap(); + assert_eq!( + unit.document_symbol(), + DocumentSymbol { + name: String::from("ent1"), + detail: Some(String::from("entity")), + kind: SymbolKind::Interface, + deprecated: None, + range: range(code, "library", "end;"), + selection_range: range1(code, "ent1"), + children: Some(vec![ + ieee_context(code), + simple_generic(code), + simple_port(code), + simple_declaration(code, ");", 2, true), + simple_statement(code, true), + ]), + } + ); + } + + #[test] + fn configuration_declaration() { + let code = " +library ieee; +configuration cfg of entity_name is + for rtl(0) + end for; +end; +"; + let design_file = parse_str(code); + let unit = design_file.design_units.first().unwrap(); + assert_eq!( + unit.document_symbol(), + DocumentSymbol { + name: String::from("cfg"), + detail: Some(String::from("configuration")), + kind: SymbolKind::Constructor, + deprecated: None, + range: range(code, "library", "end;"), + selection_range: range1(code, "cfg"), + children: Some(vec![ieee_context(code)]), + } + ); + } + + #[test] + fn package_declaration() { + let code = " +library ieee; +package pkg is + generic( + g1 : integer := 3 + ); + signal decl1 : integer; +end; +"; + let design_file = parse_str(code); + let unit = design_file.design_units.first().unwrap(); + assert_eq!( + unit.document_symbol(), + DocumentSymbol { + name: String::from("pkg"), + detail: Some(String::from("package")), + kind: SymbolKind::Package, + deprecated: None, + range: range(code, "library", "end;"), + selection_range: range1(code, "pkg"), + children: Some(vec![ + ieee_context(code), + simple_generic(code), + simple_declaration(code, ");", 1, false), + ]), + } + ); + } + + #[test] + fn package_instantiation() { + let code = " +library ieee; +package pkg_inst is new work.pkg + generic map( + gen => 1 + ); +"; + let design_file = parse_str(code); + let unit = design_file.design_units.first().unwrap(); + assert_eq!( + unit.document_symbol(), + DocumentSymbol { + name: String::from("pkg_inst"), + detail: Some(String::from("package instance")), + kind: SymbolKind::Package, + deprecated: None, + range: range(code, "library", ");"), + selection_range: range1(code, "pkg_inst"), + children: Some(vec![ieee_context(code)]), + } + ); + } + + #[test] + fn context_declaration() { + let code = " +context ctx is + library ieee; +end; +"; + let design_file = parse_str(code); + let unit = design_file.design_units.first().unwrap(); + assert_eq!( + unit.document_symbol(), + DocumentSymbol { + name: String::from("ctx"), + detail: Some(String::from("context")), + kind: SymbolKind::Namespace, + deprecated: None, + range: range(code, "context", "end;"), + selection_range: range1(code, "ctx"), + children: Some(vec![ieee_context(code)]), + } + ); + } + + #[test] + fn package_body() { + let code = " +library ieee; +package body pkg is + signal decl1 : integer; +end; +"; + let design_file = parse_str(code); + let unit = design_file.design_units.first().unwrap(); + assert_eq!( + unit.document_symbol(), + DocumentSymbol { + name: String::from("pkg"), + detail: Some(String::from("package body")), + kind: SymbolKind::Package, + deprecated: None, + range: range(code, "library", "end;"), + selection_range: range1(code, "pkg"), + children: Some(vec![ + ieee_context(code), + simple_declaration(code, "is", 1, false), + ]), + } + ); + } + + #[test] + fn architecture_body() { + let code = " +library ieee; +architecture rtl of ent is + + signal decl1 : integer; + +begin + + stmt1: decl1 <= 1; + +end architecture; +"; + let design_file = parse_str(code); + let unit = design_file.design_units.first().unwrap(); + assert_eq!( + unit.document_symbol(), + DocumentSymbol { + name: String::from("rtl"), + detail: Some(String::from("architecture of ent")), + kind: SymbolKind::Class, + deprecated: None, + range: range(code, "library", "end architecture;"), + selection_range: range1(code, "rtl"), + children: Some(vec![ + ieee_context(code), + simple_declaration(code, "is", 1, true), + simple_statement(code, true) + ]), + } + ); + } + + fn declaration( + code: &str, + name: &str, + detail: Option<&str>, + kind: SymbolKind, + ) -> DocumentSymbol { + DocumentSymbol { + name: String::from(name), + detail: detail.map(String::from), + kind, + deprecated: None, + range: range1(code, name), + selection_range: range1(code, name), + children: None, + } + } + + fn declaration2( + name: &str, + detail: Option<&str>, + kind: SymbolKind, + range: lsp_types::Range, + selection_range: lsp_types::Range, + ) -> DocumentSymbol { + DocumentSymbol { + name: String::from(name), + detail: detail.map(String::from), + kind, + deprecated: None, + range, + selection_range, + children: None, + } + } + + fn get_declarations(code: &str) -> Vec { + let design_file = parse_str(code); + let document_symbol = design_file.design_units.first().unwrap().document_symbol(); + let mut declarations = None; + for child in document_symbol.children.unwrap().iter() { + if child.name == "declarations" { + declarations = Some(child.children.clone().unwrap()); + break; + } + } + declarations.unwrap() + } + + #[test] + fn object_declaration() { + let code = " +package pkg is + signal o_signal : integer; + constant o_constant : integer := 0; + variable o_variable : integer; + shared variable o_shared_variable : integer; +end; +"; + assert_eq!( + get_declarations(code), + vec![ + declaration(code, "o_signal", None, SymbolKind::Field), + declaration(code, "o_constant", None, SymbolKind::Constant), + declaration(code, "o_variable", None, SymbolKind::Variable), + declaration(code, "o_shared_variable", None, SymbolKind::Variable), + ] + ) + } + + #[test] + fn file_declaration() { + let code = " +package pkg is + file o_file : integer; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration(code, "o_file", None, SymbolKind::File),] + ) + } + + #[test] + fn enum_type_declaration() { + let code = " +package pkg is + type t_enum is (a, b, c); +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration(code, "t_enum", Some("type"), SymbolKind::Enum),] + ) + } + + #[test] + fn integer_type_declaration() { + let code = " +package pkg is + type t_integer is range 0 to 1; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "t_integer", + Some("type"), + SymbolKind::TypeParameter + ),] + ) + } + + #[test] + fn physical_type_declaration() { + let code = " +package pkg is + type t_physical is range 0 to 1e4 units + a; + b = 1000 a; + end units t_physical; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "t_physical", + Some("type"), + SymbolKind::TypeParameter + ),] + ) + } + + #[test] + fn array_type_declaration() { + let code = " +package pkg is + type t_array is array (natural range <>) of integer; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "t_array", + Some("type"), + SymbolKind::Array + ),] + ) + } + + #[test] + fn record_stype_declaration() { + let code = " +package pkg is + type t_record is record + a : integer; + end record t_record; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "t_record", + Some("type"), + SymbolKind::Struct + ),] + ) + } + + #[test] + fn access_type_declaration() { + let code = " +package pkg is + type t_access is access integer; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration(code, "t_access", Some("type"), SymbolKind::Key),] + ) + } + + #[test] + fn incomplete_type_declaration() { + let code = " +package pkg is + type t_incomp; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "t_incomp", + Some("type"), + SymbolKind::TypeParameter + ),] + ) + } + + #[test] + fn file_type_declaration() { + let code = " +package pkg is + type t_file is file of integer; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration(code, "t_file", Some("type"), SymbolKind::File),] + ) + } + + #[test] + fn protected_type_declaration() { + let code = " +package pkg is + type t_protected is protected + end protected t_protected; + type t_protected is protected body + end protected body t_protected; +end; +"; + assert_eq!( + get_declarations(code), + vec![ + declaration(code, "t_protected", Some("type"), SymbolKind::Object), + { + let mut protected_body = + declaration(code, "t_protected", Some("type"), SymbolKind::Object); + protected_body.range.start.line += 2; + protected_body.range.end.line += 2; + protected_body.selection_range.start.line += 2; + protected_body.selection_range.end.line += 2; + protected_body + }, + ] + ) + } + + #[test] + fn subtype_declaration() { + let code = " +package pkg is + subtype t_subtype is integer range 1 to 2; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "t_subtype", + Some("type"), + SymbolKind::TypeParameter + ),] + ) + } + + #[test] + fn component_declaration() { + let code = " +package pkg is + component m_component is end component; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "m_component", + Some("component"), + SymbolKind::Interface, + ),] + ) + } + + #[test] + fn attribute() { + let code = " +package pkg is + attribute m_attribute : integer; + attribute m_attribute of m_component : component is 0; +end; +"; + assert_eq!( + get_declarations(code), + vec![ + declaration(code, "m_attribute", Some("attribute"), SymbolKind::Property), + { + let mut attr = + declaration(code, "m_attribute", Some("attribute"), SymbolKind::Property); + attr.range.start.line += 1; + attr.range.end.line += 1; + attr.selection_range.start.line += 1; + attr.selection_range.end.line += 1; + attr + } + ] + ) + } + + #[test] + fn alias_declaration() { + let code = " +package pkg is + alias m_alias is o_signal; +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration( + code, + "m_alias", + Some("alias"), + SymbolKind::TypeParameter + ),] + ) + } + + #[test] + fn subprogram_declaration() { + let code = " +package pkg is + function m_function return integer; + procedure m_procedure; +end; +"; + assert_eq!( + get_declarations(code), + vec![ + declaration(code, "m_function", Some("function"), SymbolKind::Function), + declaration(code, "m_procedure", Some("procedure"), SymbolKind::Method), + ] + ) + } + + #[test] + fn subprogram_body() { + let code = " +package body pkg is + function m_function return integer is begin end; + procedure m_procedure is begin end; +end; +"; + assert_eq!( + get_declarations(code), + vec![ + declaration(code, "m_function", Some("function"), SymbolKind::Function), + declaration(code, "m_procedure", Some("procedure"), SymbolKind::Method), + ] + ) + } + + #[test] + fn use_declaration() { + let code = " +package pkg is + use work.usepkg.all; + use use_a,use_b, use_c; +end; +"; + let mut multi_use = declaration2( + "use", + None, + SymbolKind::Namespace, + range(code, "use use_a", "c;"), + lsp_types::Range { + start: range(code, "use use_a", "c;").start, + end: range(code, "use use_a", "c;").start, + }, + ); + multi_use.children = Some({ + let mut children = vec![]; + for child in ["use_a", "use_b", "use_c"] { + let use_range = range(&code, child, child); + children.push(declaration2( + child, + Some("use"), + SymbolKind::Namespace, + use_range, + use_range, + )); + } + children + }); + assert_eq!( + get_declarations(code), + vec![ + declaration2( + "work.usepkg.all", + Some("use"), + SymbolKind::Namespace, + range(code, "use work.usepkg", ";"), + range1(code, "work.usepkg.all") + ), + multi_use + ] + ) + } + + #[test] + fn package_instantiation_declaration() { + let code = " +architecture rtl of ent is + package m_package is new gen_package; +begin +end; +"; + assert_eq!( + get_declarations(code), + vec![declaration2( + "m_package", + Some("package instance"), + SymbolKind::Package, + range(code, "package", ";"), + range1(code, "m_package") + )] + ) + } + + #[test] + fn configuration_specification() { + let code = " +architecture rtl of ent is + for all : compname use entity work.ent(sim); +begin +end; +"; + assert_eq!( + get_declarations(code), + vec![{ + let mut conf = declaration(code, "compname", None, SymbolKind::Constructor); + conf.name = String::from("configuration"); + conf + }] + ) + } + + fn statement(code: &str, name: &str, detail: Option<&str>, kind: SymbolKind) -> DocumentSymbol { + DocumentSymbol { + name: String::from(name), + detail: detail.map(String::from), + kind, + deprecated: None, + range: range1(code, name), + selection_range: range1(code, name), + children: None, + } + } + + fn statement2( + name: &str, + detail: Option<&str>, + kind: SymbolKind, + range: lsp_types::Range, + selection_range: lsp_types::Range, + ) -> DocumentSymbol { + DocumentSymbol { + name: String::from(name), + detail: detail.map(String::from), + kind, + deprecated: None, + range, + selection_range, + children: None, + } + } + + fn get_statements(code: &str) -> Vec { + let design_file = parse_str(code); + let document_symbol = design_file.design_units.first().unwrap().document_symbol(); + let mut statements = None; + for child in document_symbol.children.unwrap().iter() { + if child.name == "statements" { + statements = Some(child.children.clone().unwrap()); + break; + } + } + statements.unwrap() + } + + #[test] + fn concurrent_procedure_call() { + let code = " +architecture rtl of ent is +begin + procedure_call(par); + lbl: procedure_call(par); +end; +"; + assert_eq!( + get_statements(code), + vec![ + statement( + code, + "procedure_call", + Some("procedure call"), + SymbolKind::Method + ), + statement2( + "lbl", + Some("procedure call"), + SymbolKind::Method, + range(code, "lbl", "procedure_call"), + range1(code, "lbl"), + ) + ] + ) + } + + #[test] + fn block_statment() { + let code = " +architecture rtl of ent is +begin + block_lbl: block is + begin + end block; +end; +"; + assert_eq!( + get_statements(code), + vec![statement2( + "block_lbl", + Some("block"), + SymbolKind::Namespace, + range(code, "block_lbl", ";"), + range1(code, "block_lbl"), + )] + ) + } + + #[test] + fn process_statment() { + let code = " +architecture rtl of ent is +begin + process is begin end process; + lbl: process is begin end process; +end; +"; + assert_eq!( + get_statements(code), + vec![ + statement2( + " ", + Some("process"), + SymbolKind::Event, + range(code, "process", "end process;"), + lsp_types::Range { + start: range1(code, "process").start, + end: range1(code, "process").start, + }, + ), + statement2( + "lbl", + Some("process"), + SymbolKind::Event, + range(code, "lbl", "end process;"), + range1(code, "lbl"), + ) + ] + ) + } + + #[test] + fn concurrent_assert_statment() { + let code = " +architecture rtl of ent is +begin + assert true; + lbl: assert false; +end; +"; + assert_eq!( + get_statements(code), + vec![ + statement2( + " ", + Some("assertion"), + SymbolKind::Field, + range1(code, "true"), + range1(code, "true"), + ), + statement2( + "lbl", + Some("assertion"), + SymbolKind::Field, + range(code, "lbl", "false"), + range1(code, "lbl"), + ) + ] + ) + } + + #[test] + fn concurrent_signal_assignment() { + let code = " +architecture rtl of ent is +begin + sig <= 1; + lbl: sig <= 2; + (sig1,sig2) <= asd; + lbl2: (sig1,sig2) <= asd; +end; +"; + assert_eq!( + get_statements(code), + vec![ + statement2( + "sig", + Some("assignment"), + SymbolKind::Field, + range1(code, "sig"), + range1(code, "sig"), + ), + statement2( + "lbl", + Some("assignment"), + SymbolKind::Field, + range(code, "lbl", "sig"), + range1(code, "lbl"), + ), + statement2( + "sig1, sig2", + Some("assignment"), + SymbolKind::Field, + range1(code, "(sig1,sig2)"), + range1(code, "(sig1,sig2)"), + ), + statement2( + "lbl2", + Some("assignment"), + SymbolKind::Field, + range(code, "lbl2", "(sig1,sig2)"), + range1(code, "lbl2"), + ), + ] + ) + } + #[test] + fn instantiation_statement() { + let code = " +architecture rtl of ent is +begin + ent_i: entity work.ent_name(rtl); + cmp_i: component comp_name; + cnf_i: configuration work.cnf_name; +end; +"; + assert_eq!( + get_statements(code), + vec![ + statement2( + "ent_i", + Some("work.ent_name(rtl)"), + SymbolKind::Object, + range(code, "ent_i", "work.ent_name"), + range1(code, "ent_i"), + ), + statement2( + "cmp_i", + Some("comp_name"), + SymbolKind::Object, + range(code, "cmp_i", "comp_name"), + range1(code, "cmp_i"), + ), + statement2( + "cnf_i", + Some("work.cnf_name"), + SymbolKind::Object, + range(code, "cnf_i", "work.cnf_name"), + range1(code, "cnf_i"), + ), + ] + ) + } + + #[test] + fn for_generate_statement() { + let code = " +architecture rtl of ent is +begin + lbl: for index_name in 0 to 1 generate + begin + end generate; +end; +"; + assert_eq!( + get_statements(code), + vec![statement2( + "lbl", + Some("for generate"), + SymbolKind::Field, + range(code, "lbl", "index_name"), + range1(code, "lbl"), + )] + ) + } + + #[test] + fn if_generate_statement() { + let code = " +architecture rtl of ent is +begin + lbl: if true generate + begin + end generate; +end; +"; + assert_eq!( + get_statements(code), + vec![statement2( + "lbl", + Some("if generate"), + SymbolKind::Field, + range(code, "lbl", "true"), + range1(code, "lbl"), + )] + ) + } + + #[test] + fn case_generate_statement() { + let code = " +architecture rtl of ent is +begin + lbl: case expr generate + when others => + end generate; +end; +"; + assert_eq!( + get_statements(code), + vec![statement2( + "lbl", + Some("case generate"), + SymbolKind::Field, + range(code, "lbl", "expr"), + range1(code, "lbl"), + )] + ) + } + + #[test] + fn test_nested_document_symbol_response_from_file() { + let code = " +library ieee; + +entity ent1 is +end entity; + +"; + let (source_url, _file) = write_source_file(code); + assert_eq!( + nested_document_symbol_response_from_file(&source_url).unwrap(), + DocumentSymbolResponse::from(vec![DocumentSymbol { + name: String::from("ent1"), + detail: Some(String::from("entity")), + kind: SymbolKind::Interface, + deprecated: None, + range: range(code, "library", "end entity;"), + selection_range: range1(code, "ent1"), + children: Some(vec![ieee_context(code)]), + }]) + ); + } +} diff --git a/vhdl_ls/src/stdio_server.rs b/vhdl_ls/src/stdio_server.rs index 91a4d63b..1a80254a 100644 --- a/vhdl_ls/src/stdio_server.rs +++ b/vhdl_ls/src/stdio_server.rs @@ -2,7 +2,7 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com //! This module handles setting up `VHDLServer` for `stdio` communication. //! It also contains the main event loop for handling incoming messages from the LSP client and @@ -168,6 +168,14 @@ impl ConnectionRpcChannel { } Err(request) => request, }; + let request = match extract::(request) { + Ok((id, params)) => { + let result = server.text_document_document_symbol(¶ms); + self.send_response(lsp_server::Response::new_ok(id, result)); + return; + } + Err(request) => request, + }; let request = match extract::(request) { Ok((id, _params)) => { server.shutdown_server(); diff --git a/vhdl_ls/src/vhdl_server.rs b/vhdl_ls/src/vhdl_server.rs index 7fd3ef7f..6d9ae94d 100644 --- a/vhdl_ls/src/vhdl_server.rs +++ b/vhdl_ls/src/vhdl_server.rs @@ -2,13 +2,16 @@ // 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) 2018, Olof Kraigher olof.kraigher@gmail.com +// Copyright (c) 2021, Olof Kraigher olof.kraigher@gmail.com use lsp_types::*; use fnv::FnvHashMap; use std::collections::hash_map::Entry; +use crate::requests::document_symbol::{ + nested_document_symbol_response_from_file, nested_document_symbol_response_from_source, +}; use crate::rpc_channel::{MessageChannel, RpcChannel}; use std::io; use std::path::{Path, PathBuf}; @@ -208,6 +211,14 @@ impl VHDLServer { pub fn text_document_references(&mut self, params: &ReferenceParams) -> Vec { self.mut_server().text_document_references(¶ms) } + + // textDocument/documentSymbol + pub fn text_document_document_symbol( + &mut self, + params: &DocumentSymbolParams, + ) -> Option { + self.mut_server().text_document_document_symbol(¶ms) + } } struct InitializedVHDLServer { @@ -255,6 +266,7 @@ impl InitializedVHDLServer { definition_provider: Some(true), hover_provider: Some(true), references_provider: Some(true), + document_symbol_provider: Some(true), ..Default::default() }; @@ -444,6 +456,16 @@ impl InitializedVHDLServer { Vec::new() } } + + pub fn text_document_document_symbol( + &mut self, + params: &DocumentSymbolParams, + ) -> Option { + self.project + .get_source(&uri_to_file_name(¶ms.text_document.uri)) + .map(|source| nested_document_symbol_response_from_source(&source)) + .or_else(|| nested_document_symbol_response_from_file(¶ms.text_document.uri)) + } } fn srcpos_to_location(pos: &SrcPos) -> Location {