diff --git a/vhdl_lang/src/analysis/concurrent.rs b/vhdl_lang/src/analysis/concurrent.rs index 8a6f3b25..201f8e23 100644 --- a/vhdl_lang/src/analysis/concurrent.rs +++ b/vhdl_lang/src/analysis/concurrent.rs @@ -64,6 +64,8 @@ impl<'a> AnalyzeContext<'a> { sensitivity_list, decl, statements, + start_token: _, + semi_token: _, } = 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 1b666f9e..40037544 100644 --- a/vhdl_lang/src/analysis/design_unit.rs +++ b/vhdl_lang/src/analysis/design_unit.rs @@ -75,10 +75,10 @@ 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.items, 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.items, diagnostics)?; } self.analyze_declarative_part(&mut primary_region, &mut unit.decl, diagnostics)?; self.analyze_concurrent_part(&mut primary_region, &mut unit.statements, diagnostics)?; @@ -148,7 +148,7 @@ 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.items, diagnostics)?; } self.analyze_declarative_part(&mut primary_region, &mut unit.decl, diagnostics)?; diff --git a/vhdl_lang/src/ast.rs b/vhdl_lang/src/ast.rs index 034b1df2..7654a165 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) 2020, 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 @@ -24,6 +24,8 @@ pub use any_design_unit::*; use crate::data::*; +pub use crate::data::KeyWordToken; + /// LRM 15.8 Bit string literals #[derive(PartialEq, Copy, Clone, Debug)] pub enum BaseSpecifier { @@ -622,6 +624,15 @@ pub enum InterfaceDeclaration { Package(InterfacePackageDeclaration), } +#[derive(PartialEq, Debug, Clone)] +pub struct InterfaceList { + pub items: Vec, + + // Tokens + pub start_token: KeyWordToken, // port/generiv/parameter + pub semi_token: KeyWordToken, +} + #[derive(PartialEq, Debug, Clone, Copy)] pub enum Mode { In, @@ -631,11 +642,6 @@ pub enum Mode { Linkage, } -#[derive(PartialEq, Debug, Clone)] -pub struct PortClause { - pub port_list: Vec, -} - /// LRM 6.8 Component declarations #[derive(PartialEq, Debug, Clone)] pub struct ComponentDeclaration { @@ -859,6 +865,10 @@ pub struct BlockStatement { pub header: BlockHeader, pub decl: Vec, pub statements: Vec, + + // Tokens + pub block_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 11.2 Block statement @@ -883,6 +893,10 @@ pub struct ProcessStatement { pub sensitivity_list: Option, pub decl: Vec, pub statements: Vec, + + // Tokens + pub start_token: KeyWordToken, // postponed/process + pub semi_token: KeyWordToken, } /// LRM 11.4 Concurrent procedure call statements @@ -997,6 +1011,12 @@ pub enum ContextItem { pub struct ContextDeclaration { pub ident: Ident, pub items: ContextClause, + + // Tokens + pub context_token: KeyWordToken, + pub is_token: KeyWordToken, + pub end_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 4.9 Package instatiation declaration @@ -1006,6 +1026,10 @@ pub struct PackageInstantiation { pub ident: Ident, pub package_name: WithPos, pub generic_map: Option>, + + // Tokens + pub package_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 7.3 Configuration specification @@ -1093,6 +1117,12 @@ pub struct ConfigurationDeclaration { pub decl: Vec, pub vunit_bind_inds: Vec, pub block_config: BlockConfiguration, + + // Tokens + pub configuration_token: KeyWordToken, + pub is_token: KeyWordToken, + pub end_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 3.2 Entity declarations @@ -1100,10 +1130,17 @@ pub struct ConfigurationDeclaration { pub struct EntityDeclaration { pub context_clause: ContextClause, pub ident: Ident, - pub generic_clause: Option>, - pub port_clause: Option>, + pub generic_clause: Option, + pub port_clause: Option, pub decl: Vec, pub statements: Vec, + + // Tokens + pub entity_token: KeyWordToken, + pub is_token: KeyWordToken, + pub begin_token: Option, + pub end_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 3.3 Architecture bodies #[derive(PartialEq, Debug, Clone)] @@ -1113,6 +1150,13 @@ pub struct ArchitectureBody { pub entity_name: WithRef, pub decl: Vec, pub statements: Vec, + + // Tokens + pub architecture_token: KeyWordToken, + pub is_token: KeyWordToken, + pub begin_token: KeyWordToken, + pub end_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 4.7 Package declarations @@ -1120,8 +1164,14 @@ pub struct ArchitectureBody { pub struct PackageDeclaration { pub context_clause: ContextClause, pub ident: Ident, - pub generic_clause: Option>, + pub generic_clause: Option, pub decl: Vec, + + // Tokens + pub package_token: KeyWordToken, + pub is_token: KeyWordToken, + pub end_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 4.8 Package bodies @@ -1130,6 +1180,12 @@ pub struct PackageBody { pub context_clause: ContextClause, pub ident: WithRef, pub decl: Vec, + + // Tokens + pub package_token: KeyWordToken, + pub is_token: KeyWordToken, + pub end_token: KeyWordToken, + pub semi_token: KeyWordToken, } /// LRM 13.1 Design units diff --git a/vhdl_lang/src/ast/search.rs b/vhdl_lang/src/ast/search.rs index ff87d4f0..14e9f2df 100644 --- a/vhdl_lang/src/ast/search.rs +++ b/vhdl_lang/src/ast/search.rs @@ -389,6 +389,8 @@ impl Search for LabeledConcurrentStatement { sensitivity_list, decl, statements, + start_token: _, + semi_token: _, } = process; return_if_found!(sensitivity_list.search(searcher)); return_if_found!(decl.search(searcher)); @@ -883,6 +885,13 @@ impl Search for InterfaceDeclaration { } } +impl Search for InterfaceList { + fn search(&self, searcher: &mut impl Searcher) -> SearchResult { + return_if_found!(self.items.search(searcher)); + NotFound + } +} + impl Search for SubprogramDeclaration { fn search(&self, searcher: &mut impl Searcher) -> SearchResult { match self { diff --git a/vhdl_lang/src/config.rs b/vhdl_lang/src/config.rs index edf011f7..eeea1da7 100644 --- a/vhdl_lang/src/config.rs +++ b/vhdl_lang/src/config.rs @@ -2,21 +2,18 @@ // 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) 2020, Olof Kraigher olof.kraigher@gmail.com //! Configuration of the design hierarchy and other settings -use toml; - -use self::fnv::FnvHashMap; -use self::toml::Value; use crate::data::*; -use fnv; +use fnv::FnvHashMap; use std::env; use std::fs::File; use std::io; use std::io::prelude::*; use std::path::Path; +use toml::Value; #[derive(Clone, PartialEq, Default, Debug)] pub struct Config { diff --git a/vhdl_lang/src/data.rs b/vhdl_lang/src/data.rs index 6ba7a451..07028445 100644 --- a/vhdl_lang/src/data.rs +++ b/vhdl_lang/src/data.rs @@ -12,6 +12,7 @@ mod message; mod source; mod symbol_table; +pub use crate::syntax::KeyWordToken; pub use contents::*; pub use diagnostic::*; pub use latin_1::*; diff --git a/vhdl_lang/src/data/source.rs b/vhdl_lang/src/data/source.rs index 51f19454..e048a7ef 100644 --- a/vhdl_lang/src/data/source.rs +++ b/vhdl_lang/src/data/source.rs @@ -2,11 +2,10 @@ // 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) 2020, Olof Kraigher olof.kraigher@gmail.com use super::contents::Contents; use super::diagnostic::{Diagnostic, DiagnosticResult}; -use pad; use parking_lot::{RwLock, RwLockReadGuard}; use std::cmp::{max, min}; use std::collections::hash_map::DefaultHasher; @@ -399,7 +398,7 @@ impl SrcPos { context_lines: u32, ) -> (usize, String) { let lines = self.get_line_context(context_lines, contents); - use self::pad::{Alignment, PadStr}; + use pad::{Alignment, PadStr}; // +1 since lines are shown with 1-index let lineno_len = (self.range.start.line + context_lines + 1) .to_string() diff --git a/vhdl_lang/src/data/symbol_table.rs b/vhdl_lang/src/data/symbol_table.rs index 1d51a345..85aaae79 100644 --- a/vhdl_lang/src/data/symbol_table.rs +++ b/vhdl_lang/src/data/symbol_table.rs @@ -2,14 +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) 2020, Olof Kraigher olof.kraigher@gmail.com use super::latin_1::Latin1String; use parking_lot::RwLock; use std::sync::Arc; -use self::fnv::FnvHashMap; -use fnv; +use fnv::FnvHashMap; /// Represents a unique string symbol. /// diff --git a/vhdl_lang/src/syntax.rs b/vhdl_lang/src/syntax.rs index 173a4ca2..3dd43b8e 100644 --- a/vhdl_lang/src/syntax.rs +++ b/vhdl_lang/src/syntax.rs @@ -32,4 +32,4 @@ mod waveform; pub mod test; pub use parser::{ParserResult, VHDLParser}; -pub use tokens::Symbols; +pub use tokens::{KeyWordToken, Symbols}; diff --git a/vhdl_lang/src/syntax/component_declaration.rs b/vhdl_lang/src/syntax/component_declaration.rs index 3e3a9e2f..f3212631 100644 --- a/vhdl_lang/src/syntax/component_declaration.rs +++ b/vhdl_lang/src/syntax/component_declaration.rs @@ -8,13 +8,13 @@ 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::ast::{ComponentDeclaration, InterfaceList}; use crate::data::{Diagnostic, DiagnosticHandler}; 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,15 @@ 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(InterfaceList { + items: new_list, + start_token: token.into(), + semi_token: semi_token.into(), + }); } } _ => break, @@ -39,7 +43,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 +51,15 @@ 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(InterfaceList { + items: new_list, + start_token: token.into(), + semi_token: semi_token.into(), + }); } } Generic => { @@ -92,8 +100,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(), |list| list.items), + port_list: port_list.map_or(Vec::new(), |list| list.items), }) } @@ -101,7 +109,7 @@ pub fn parse_component_declaration( mod tests { use super::*; - use crate::ast::Ident; + use crate::ast::{Ident, InterfaceDeclaration}; use crate::syntax::test::Code; fn to_component( @@ -222,7 +230,14 @@ end "Duplicate generic clause" )] ); - assert_eq!(result, Ok(Some(vec![code.s1("foo : natural").generic()])),); + assert_eq!( + result, + Ok(Some(InterfaceList { + items: vec![code.s1("foo : natural").generic()], + start_token: code.keyword_token(Generic, 1), + semi_token: code.keyword_token(SemiColon, 1) + })), + ); } #[test] @@ -246,7 +261,14 @@ end "Duplicate port clause" )] ); - assert_eq!(result, Ok(Some(vec![code.s1("foo : natural").port()])),); + assert_eq!( + result, + Ok(Some(InterfaceList { + items: vec![code.s1("foo : natural").port()], + start_token: code.keyword_token(Port, 1), + semi_token: code.keyword_token(SemiColon, 1) + })), + ); } #[test] @@ -270,6 +292,13 @@ end "Generic clause must come before port clause" )] ); - assert_eq!(result, Ok(Some(vec![code.s1("foo : natural").port()])),); + assert_eq!( + result, + Ok(Some(InterfaceList { + items: vec![code.s1("foo : natural").port()], + start_token: code.keyword_token(Port, 1), + semi_token: code.keyword_token(SemiColon, 1) + })), + ); } } diff --git a/vhdl_lang/src/syntax/concurrent_statement.rs b/vhdl_lang/src/syntax/concurrent_statement.rs index 6cea181b..82acba61 100644 --- a/vhdl_lang/src/syntax/concurrent_statement.rs +++ b/vhdl_lang/src/syntax/concurrent_statement.rs @@ -26,6 +26,7 @@ use crate::data::*; /// LRM 11.2 Block statement pub fn parse_block_statement( + block_token: Token, stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { @@ -48,12 +49,14 @@ 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)?.into(); Ok(BlockStatement { guard_condition, header, decl, statements, + block_token: block_token.into(), + semi_token, }) } @@ -153,9 +156,10 @@ fn parse_block_header( /// LRM 11.3 Process statement pub fn parse_process_statement( stream: &mut TokenStream, - postponed: bool, + start_token: Token, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { + let postponed = start_token.kind == Postponed; let token = stream.peek_expect()?; let sensitivity_list = { match token.kind { @@ -207,12 +211,14 @@ 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)?.into(); Ok(ProcessStatement { postponed, sensitivity_list, decl, statements, + start_token: start_token.into(), + semi_token, }) } @@ -557,10 +563,10 @@ pub fn parse_concurrent_statement( try_token_kind!( token, Block => { - ConcurrentStatement::Block(parse_block_statement(stream, diagnostics)?) + ConcurrentStatement::Block(parse_block_statement(token, stream, diagnostics)?) }, Process => { - ConcurrentStatement::Process(parse_process_statement(stream, false, diagnostics)?) + ConcurrentStatement::Process(parse_process_statement(stream, token, diagnostics)?) }, Component => { let unit = InstantiatedUnit::Component(parse_selected_name(stream)?); @@ -589,13 +595,13 @@ 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 token = stream.expect()?; - match token.kind { - Process => ConcurrentStatement::Process(parse_process_statement(stream, true, diagnostics)?), + let next_token = stream.expect()?; + match next_token.kind { + Process => ConcurrentStatement::Process(parse_process_statement(stream, token, diagnostics)?), Assert => ConcurrentStatement::Assert(parse_concurrent_assert_statement(stream, true)?), With => ConcurrentStatement::Assignment(parse_selected_signal_assignment(stream, true)?), _ => { - let target = parse_name_initial_token(stream, token)?.map_into(Target::Name); + let target = parse_name_initial_token(stream, next_token)?.map_into(Target::Name); stream.expect_kind(SemiColon)?; ConcurrentStatement::ProcedureCall(to_procedure_call(target, true)?) } @@ -799,6 +805,8 @@ end block; label: Some(code.s1("name2").ident()), statement: ConcurrentStatement::ProcedureCall(call), }], + block_token: code.keyword_token(Block, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -824,6 +832,8 @@ end block name; }, decl: vec![], statements: vec![], + block_token: code.keyword_token(Block, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -849,6 +859,8 @@ end block; }, decl: vec![], statements: vec![], + block_token: code.keyword_token(Block, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -874,6 +886,8 @@ end block; }, decl: vec![], statements: vec![], + block_token: code.keyword_token(Block, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -903,6 +917,8 @@ end block; }, decl: vec![], statements: vec![], + block_token: code.keyword_token(Block, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -923,6 +939,8 @@ end process; sensitivity_list: None, decl: vec![], statements: vec![], + start_token: code.keyword_token(Process, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -943,6 +961,8 @@ end process name; sensitivity_list: None, decl: vec![], statements: vec![], + start_token: code.keyword_token(Process, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, Some(code.s1("name").ident())); @@ -963,6 +983,8 @@ end process; sensitivity_list: None, decl: vec![], statements: vec![], + start_token: code.keyword_token(Postponed, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -983,6 +1005,8 @@ end postponed process; sensitivity_list: None, decl: vec![], statements: vec![], + start_token: code.keyword_token(Postponed, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -1004,6 +1028,8 @@ end postponed process; sensitivity_list: None, decl: Vec::new(), statements: Vec::new(), + start_token: code.keyword_token(Process, 1), + semi_token: code.keyword_token(SemiColon, -1), }; assert_eq!( diagnostics, @@ -1032,6 +1058,8 @@ end process; ])), decl: vec![], statements: vec![], + start_token: code.keyword_token(Process, 1), + semi_token: code.keyword_token(SemiColon, -1), }; let stmt = code.with_stream_no_diagnostics(parse_labeled_concurrent_statement); assert_eq!(stmt.label, None); @@ -1053,6 +1081,8 @@ end process; sensitivity_list: Some(SensitivityList::Names(Vec::new())), decl: Vec::new(), statements: Vec::new(), + start_token: code.keyword_token(Process, 1), + semi_token: code.keyword_token(SemiColon, -1), }; assert_eq!( diagnostics, @@ -1084,6 +1114,8 @@ end process; code.s1("foo <= true;").sequential_statement(), code.s1("wait;").sequential_statement(), ], + start_token: code.keyword_token(Process, 1), + semi_token: code.keyword_token(SemiColon, -1), }; 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..a7297fbd 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) 2020, Olof Kraigher olof.kraigher@gmail.com use super::common::error_on_end_identifier_mismatch; use super::common::ParseResult; @@ -268,11 +268,11 @@ pub fn parse_configuration_declaration( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Configuration)?; + let configuration_token = stream.expect_kind(Configuration)?.into(); let ident = stream.expect_ident()?; stream.expect_kind(Of)?; let entity_name = parse_selected_name(stream)?; - stream.expect_kind(Is)?; + let is_token = stream.expect_kind(Is)?.into(); let mut decl = Vec::new(); let vunit_bind_inds = loop { @@ -295,13 +295,13 @@ pub fn parse_configuration_declaration( stream.expect_kind(For)?; let block_config = parse_block_configuration_known_keyword(stream, diagnostics)?; - stream.expect_kind(End)?; + let end_token = stream.expect_kind(End)?.into(); stream.pop_if_kind(Configuration)?; 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)?.into(); Ok(ConfigurationDeclaration { context_clause: ContextClause::default(), ident, @@ -309,6 +309,10 @@ pub fn parse_configuration_declaration( decl, vunit_bind_inds, block_config, + configuration_token, + is_token, + end_token, + semi_token, }) } @@ -352,6 +356,7 @@ pub fn parse_configuration_specification( mod tests { use super::*; use crate::syntax::test::Code; + use pretty_assertions::assert_eq; #[test] fn empty_configuration() { @@ -375,7 +380,11 @@ end; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -402,7 +411,11 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -433,7 +446,11 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -466,7 +483,11 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -493,7 +514,11 @@ end configuration cfg; block_spec: code.s1("rtl(0)").name(), use_clauses: vec![], items: vec![], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -535,7 +560,11 @@ end configuration cfg; items: vec![], }) ], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -580,7 +609,11 @@ end configuration cfg; items: vec![], }), }),], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -636,7 +669,11 @@ end configuration cfg; items: vec![], }), }),], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -683,7 +720,11 @@ end configuration cfg; vunit_bind_inds: Vec::new(), block_config: None, }),], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -761,7 +802,11 @@ end configuration cfg; block_config: None, }) ], - } + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } diff --git a/vhdl_lang/src/syntax/context.rs b/vhdl_lang/src/syntax/context.rs index b43dbe58..39cd7c6e 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) 2020, Olof Kraigher olof.kraigher@gmail.com use super::common::error_on_end_identifier_mismatch; use super::common::ParseResult; @@ -89,10 +89,11 @@ pub fn parse_context( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - let context_token = stream.expect_kind(Context)?; + let context_token = stream.expect_kind(Context)?.into(); let name = parse_name(stream)?; - if stream.skip_if_kind(Is)? { + if let Some(is_token) = stream.pop_if_kind(Is)? { let mut items = Vec::with_capacity(16); + let end_token; let end_ident; loop { let token = stream.expect()?; @@ -102,14 +103,14 @@ pub fn parse_context( Use => items.push(parse_use_clause_no_keyword(token, stream)?.map_into(ContextItem::Use)), Context => items.push(parse_context_reference_no_keyword(token, stream)?.map_into(ContextItem::Context)), End => { + end_token = token.into(); stream.pop_if_kind(Context)?; end_ident = stream.pop_optional_ident()?; - stream.expect_kind(SemiColon)?; break; } ) } - + let semi_token = stream.expect_kind(SemiColon)?.into(); let ident = to_simple_name(name)?; diagnostics.push_some(error_on_end_identifier_mismatch(&ident, &end_ident)); @@ -117,6 +118,10 @@ pub fn parse_context( Ok(DeclarationOrReference::Declaration(ContextDeclaration { ident, items, + context_token, + is_token: is_token.into(), + end_token, + semi_token, })) } else { // Context reference @@ -141,6 +146,7 @@ mod tests { use crate::data::Diagnostic; use crate::syntax::test::Code; + use pretty_assertions::assert_eq; #[test] fn test_library_clause_single_name() { @@ -246,13 +252,17 @@ context ident is end context ident; ", ]; - for variant in variants { + for variant in variants.iter() { let code = Code::new(variant); assert_eq!( code.with_stream_no_diagnostics(parse_context), DeclarationOrReference::Declaration(ContextDeclaration { ident: code.s1("ident").ident(), - items: vec![] + items: vec![], + context_token: code.keyword_token(Context, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), }) ); } @@ -278,7 +288,11 @@ end context ident2; context, DeclarationOrReference::Declaration(ContextDeclaration { ident: code.s1("ident").ident(), - items: vec![] + items: vec![], + context_token: code.keyword_token(Context, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), }) ); } @@ -317,7 +331,11 @@ end context; }), code.s1("context foo.ctx;") ), - ] + ], + context_token: code.keyword_token(Context, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), }) ) } diff --git a/vhdl_lang/src/syntax/declarative_part.rs b/vhdl_lang/src/syntax/declarative_part.rs index 8005fb6e..14362600 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) 2020, Olof Kraigher olof.kraigher@gmail.com use super::alias_declaration::parse_alias_declaration; use super::attributes::parse_attribute; @@ -19,27 +19,32 @@ use crate::ast::{ContextClause, Declaration, PackageInstantiation}; use crate::data::DiagnosticHandler; pub fn parse_package_instantiation(stream: &mut TokenStream) -> ParseResult { - stream.expect_kind(Package)?; + let package_token = stream.expect_kind(Package)?.into(); 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.into()) }, - SemiColon => None); + SemiColon => { + (None, token.into()) + } + ); Ok(PackageInstantiation { context_clause: ContextClause::default(), ident, package_name, generic_map, + package_token, + semi_token, }) } @@ -63,6 +68,7 @@ fn check_declarative_part(token: &Token, may_end: bool, may_begin: bool) -> Pars } } } + pub fn parse_declarative_part( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, @@ -75,6 +81,18 @@ pub fn parse_declarative_part( Ok(decl) } +pub fn parse_declarative_part_end_token( + stream: &mut TokenStream, + diagnostics: &mut dyn DiagnosticHandler, + begin_is_end: bool, +) -> ParseResult<(Vec, Token)> { + let expected_end_token = if begin_is_end { Begin } else { End }; + let decl = parse_declarative_part_leave_end_token(stream, diagnostics)?; + let end_token = stream.peek_expect()?; + stream.expect_kind(expected_end_token).log(diagnostics); + Ok((decl, end_token)) +} + pub fn parse_declarative_part_leave_end_token( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, @@ -177,7 +195,9 @@ 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, + package_token: code.keyword_token(Package, 1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -203,7 +223,9 @@ package ident is new lib.foo.bar foo => bar )") .association_list() - ) + ), + package_token: code.keyword_token(Package, 1), + semi_token: code.keyword_token(SemiColon, -1), } ); } diff --git a/vhdl_lang/src/syntax/design_unit.rs b/vhdl_lang/src/syntax/design_unit.rs index 9c003434..2a7efbcc 100644 --- a/vhdl_lang/src/syntax/design_unit.rs +++ b/vhdl_lang/src/syntax/design_unit.rs @@ -2,22 +2,22 @@ // 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) 2020, Olof Kraigher olof.kraigher@gmail.com use super::tokens::{Kind::*, TokenStream}; 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_end_token, parse_declarative_part_leave_end_token, + parse_package_instantiation, }; -use super::interface_declaration::parse_generic_interface_list; use crate::ast::*; use crate::data::*; @@ -27,10 +27,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)?.into(); let ident = stream.expect_ident()?; - stream.expect_kind(Is)?; + let is_token = stream.expect_kind(Is)?.into(); let generic_clause = parse_optional_generic_list(stream, diagnostics)?; let port_clause = parse_optional_port_list(stream, diagnostics)?; @@ -38,17 +38,20 @@ pub fn parse_entity_declaration( let decl = parse_declarative_part_leave_end_token(stream, diagnostics)?; let token = stream.expect()?; - let statements = try_token_kind!( + let (begin_token, statements, end_token) = try_token_kind!( token, - End => Vec::new(), - Begin => parse_labeled_concurrent_statements(stream, diagnostics)? + End => (None, Vec::new(), token.into()), + Begin => { + let (statements, end_token) = parse_labeled_concurrent_statements_end_token(stream, diagnostics)?; + (Some(token.into()), statements, end_token.into()) + } ); 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)?.into(); Ok(EntityDeclaration { context_clause: ContextClause::default(), ident, @@ -56,6 +59,11 @@ pub fn parse_entity_declaration( port_clause, decl, statements, + entity_token, + is_token, + begin_token, + end_token, + semi_token, }) } @@ -64,15 +72,18 @@ pub fn parse_architecture_body( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Architecture)?; + let architecture_token = stream.expect_kind(Architecture)?.into(); let ident = stream.expect_ident()?; stream.expect_kind(Of)?; let entity_name = stream.expect_ident()?; - stream.expect_kind(Is)?; + let is_token = stream.expect_kind(Is)?.into(); - let decl = parse_declarative_part(stream, diagnostics, true)?; + let (decl, begin_token) = parse_declarative_part_end_token(stream, diagnostics, true) + .map(|(decl, begin_token)| (decl, begin_token.into()))?; - let statements = parse_labeled_concurrent_statements(stream, diagnostics)?; + let (statements, end_token) = + parse_labeled_concurrent_statements_end_token(stream, diagnostics) + .map(|(statements, end_token)| (statements, end_token.into()))?; stream.pop_if_kind(Architecture)?; let end_ident = stream.pop_optional_ident()?; @@ -80,7 +91,7 @@ pub fn parse_architecture_body( diagnostics.push(diagnostic); } - stream.expect_kind(SemiColon)?; + let semi_token = stream.expect_kind(SemiColon)?.into(); Ok(ArchitectureBody { context_clause: ContextClause::default(), @@ -88,6 +99,11 @@ pub fn parse_architecture_body( entity_name: entity_name.into_ref(), decl, statements, + architecture_token, + is_token, + begin_token, + end_token, + semi_token, }) } @@ -96,32 +112,29 @@ pub fn parse_package_declaration( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Package)?; + let package_token = stream.expect_kind(Package)?.into(); let ident = stream.expect_ident()?; - stream.expect_kind(Is)?; - let generic_clause = { - if stream.skip_if_kind(Generic)? { - let decl = parse_generic_interface_list(stream, diagnostics)?; - stream.expect_kind(SemiColon)?; - Some(decl) - } else { - None - } - }; - let decl = parse_declarative_part(stream, diagnostics, false)?; + let is_token = stream.expect_kind(Is)?.into(); + let generic_clause = parse_optional_generic_list(stream, diagnostics)?; + let (decl, end_token) = parse_declarative_part_end_token(stream, diagnostics, false) + .map(|(decl, end_token)| (decl, end_token.into()))?; 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)?.into(); Ok(PackageDeclaration { context_clause: ContextClause::default(), ident, generic_clause, decl, + package_token, + is_token, + end_token, + semi_token, }) } @@ -130,12 +143,13 @@ pub fn parse_package_body( stream: &mut TokenStream, diagnostics: &mut dyn DiagnosticHandler, ) -> ParseResult { - stream.expect_kind(Package)?; + let package_token = stream.expect_kind(Package)?.into(); 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)?.into(); + let (decl, end_token) = parse_declarative_part_end_token(stream, diagnostics, false) + .map(|(decl, end_token)| (decl, end_token.into()))?; if stream.skip_if_kind(Package)? { stream.expect_kind(Body)?; } @@ -143,12 +157,16 @@ 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)?.into(); Ok(PackageBody { context_clause: ContextClause::default(), ident: ident.into_ref(), decl, + package_token, + is_token, + end_token, + semi_token, }) } @@ -281,6 +299,7 @@ mod tests { use crate::data::Diagnostic; use crate::syntax::test::{check_diagnostics, check_no_diagnostics, Code}; + use pretty_assertions::assert_eq; fn parse_str(code: &str) -> (Code, DesignFile, Vec) { let code = Code::new(code); @@ -309,14 +328,25 @@ mod tests { } /// An simple entity with only a name - fn simple_entity(ident: Ident) -> AnyDesignUnit { + fn simple_entity( + code: Code, + ident: &str, + entity_occurance: isize, + semi_occurance: isize, + ) -> AnyDesignUnit { + let ident = code.s1(ident).ident(); AnyDesignUnit::Primary(AnyPrimaryUnit::Entity(EntityDeclaration { context_clause: ContextClause::default(), - ident, + ident: ident.clone(), generic_clause: None, port_clause: None, decl: vec![], statements: vec![], + entity_token: code.keyword_token(Entity, entity_occurance), + is_token: code.keyword_token(Is, semi_occurance), + begin_token: None, + end_token: code.keyword_token(End, semi_occurance), + semi_token: code.keyword_token(SemiColon, semi_occurance), })) } @@ -330,7 +360,7 @@ end entity; ); assert_eq!( design_file.design_units, - [simple_entity(code.s1("myent").ident())] + [simple_entity(code, "myent", 1, 1)] ); let (code, design_file) = parse_ok( @@ -341,7 +371,7 @@ end entity myent; ); assert_eq!( design_file.design_units, - [simple_entity(code.s1("myent").ident())] + [simple_entity(code, "myent", 1, 1)] ); } @@ -359,10 +389,19 @@ end entity; EntityDeclaration { context_clause: ContextClause::default(), ident: code.s1("myent").ident(), - generic_clause: Some(Vec::new()), + generic_clause: Some(InterfaceList { + items: Vec::new(), + start_token: code.keyword_token(Generic, 1), + semi_token: code.keyword_token(SemiColon, 1), + }), port_clause: None, decl: vec![], statements: vec![], + entity_token: code.keyword_token(Entity, 1), + is_token: code.keyword_token(Is, 1), + begin_token: None, + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -386,10 +425,19 @@ end entity; item: code.symbol("myent"), pos: code.s1("myent").pos() }, - generic_clause: Some(vec![code.s1("runner_cfg : string").generic()]), + generic_clause: Some(InterfaceList { + items: vec![code.s1("runner_cfg : string").generic()], + start_token: code.keyword_token(Generic, 1), + semi_token: code.keyword_token(SemiColon, 1), + }), port_clause: None, decl: vec![], statements: vec![], + entity_token: code.keyword_token(Entity, 1), + is_token: code.keyword_token(Is, 1), + begin_token: None, + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -409,9 +457,18 @@ end entity; context_clause: ContextClause::default(), ident: code.s1("myent").ident(), generic_clause: None, - port_clause: Some(vec![]), + port_clause: Some(InterfaceList { + items: vec![], + start_token: code.keyword_token(Port, 1), + semi_token: code.keyword_token(SemiColon, 1), + }), decl: vec![], statements: vec![], + entity_token: code.keyword_token(Entity, 1), + is_token: code.keyword_token(Is, 1), + begin_token: None, + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -434,6 +491,11 @@ end entity; port_clause: None, decl: vec![], statements: vec![], + entity_token: code.keyword_token(Entity, 1), + is_token: code.keyword_token(Is, 1), + begin_token: Some(code.keyword_token(Begin, 1)), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -456,6 +518,11 @@ end entity; port_clause: None, decl: code.s1("constant foo : natural := 0;").declarative_part(), statements: vec![], + entity_token: code.keyword_token(Entity, 1), + is_token: code.keyword_token(Is, 1), + begin_token: None, + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -479,6 +546,11 @@ end entity; port_clause: None, decl: vec![], statements: vec![code.s1("check(clk, valid);").concurrent_statement()], + entity_token: code.keyword_token(Entity, 1), + is_token: code.keyword_token(Is, 1), + begin_token: Some(code.keyword_token(Begin, 1)), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -503,22 +575,27 @@ 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.clone(), "myent", 1, 1), + simple_entity(code.clone(), "myent2", 3, 2), + simple_entity(code.clone(), "myent3", 5, 3), + simple_entity(code.clone(), "myent4", 6, 4), ] ); } // 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(), + ident: code.s1(ident).ident(), + entity_name: code.s1(entity_name).ident().into_ref(), decl: Vec::new(), statements: vec![], + architecture_token: code.keyword_token(Architecture, 1), + is_token: code.keyword_token(Is, 1), + begin_token: code.keyword_token(Begin, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), })) } @@ -533,10 +610,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 +625,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 +640,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")] ); } @@ -591,6 +659,10 @@ end package; ident: code.s1("pkg_name").ident(), generic_clause: None, decl: vec![], + package_token: code.keyword_token(Package, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -617,6 +689,10 @@ end package; constant bar : natural := 0; ") .declarative_part(), + package_token: code.keyword_token(Package, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } @@ -638,17 +714,22 @@ 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(InterfaceList { + items: vec![code.s1("type foo").generic(), code.s1("type bar").generic()], + start_token: code.keyword_token(Generic, 1), + semi_token: code.keyword_token(SemiColon, 2), + }), + decl: vec![], + package_token: code.keyword_token(Package, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ); } #[test] - fn context_clause_associated_with_design_units() { + fn context_clause_associated_with_entity_declaration() { let (code, design_file) = parse_ok( " library lib; @@ -676,12 +757,212 @@ end entity; port_clause: None, decl: vec![], statements: vec![], + entity_token: code.keyword_token(Entity, 1), + is_token: code.keyword_token(Is, 1), + begin_token: None, + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), } ))] } ); } + #[test] + fn context_clause_associated_with_architecture_body() { + let (code, design_file) = parse_ok( + " +library lib; +use lib.foo; + +architecture rtl of myent is +begin +end; + +", + ); + assert_eq!( + design_file, + DesignFile { + design_units: vec![AnyDesignUnit::Secondary(AnySecondaryUnit::Architecture( + ArchitectureBody { + context_clause: vec![ + code.s1("library lib;") + .library_clause() + .map_into(ContextItem::Library), + code.s1("use lib.foo;") + .use_clause() + .map_into(ContextItem::Use), + ], + ident: code.s1("rtl").ident(), + entity_name: code.s1("myent").ident().into_ref(), + decl: Vec::new(), + statements: vec![], + architecture_token: code.keyword_token(Architecture, 1), + is_token: code.keyword_token(Is, 1), + begin_token: code.keyword_token(Begin, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), + } + ))], + } + ); + } + + #[test] + fn context_clause_associated_with_package_declaration() { + let (code, design_file) = parse_ok( + " +library lib; +use lib.foo; + +package pkg_name is +end; + +", + ); + assert_eq!( + design_file, + DesignFile { + design_units: vec![AnyDesignUnit::Primary(AnyPrimaryUnit::Package( + PackageDeclaration { + context_clause: vec![ + code.s1("library lib;") + .library_clause() + .map_into(ContextItem::Library), + code.s1("use lib.foo;") + .use_clause() + .map_into(ContextItem::Use), + ], + ident: code.s1("pkg_name").ident(), + generic_clause: None, + decl: Vec::new(), + package_token: code.keyword_token(Package, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), + } + ))], + } + ); + } + + #[test] + fn context_clause_associated_with_package_body() { + let (code, design_file) = parse_ok( + " +library lib; +use lib.foo; + +package body pkg_name is +end; + +", + ); + assert_eq!( + design_file, + DesignFile { + design_units: vec![AnyDesignUnit::Secondary(AnySecondaryUnit::PackageBody( + PackageBody { + context_clause: vec![ + code.s1("library lib;") + .library_clause() + .map_into(ContextItem::Library), + code.s1("use lib.foo;") + .use_clause() + .map_into(ContextItem::Use), + ], + ident: code.s1("pkg_name").ident().into_ref(), + decl: Vec::new(), + package_token: code.keyword_token(Package, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), + } + ))], + } + ); + } + + #[test] + fn context_clause_associated_with_configuration_declaration() { + let (code, design_file) = parse_ok( + " +library lib; +use lib.foo; + +configuration cfg of entity_name is + for rtl(0) + end for; +end; +", + ); + assert_eq!( + design_file, + DesignFile { + design_units: vec![AnyDesignUnit::Primary(AnyPrimaryUnit::Configuration( + ConfigurationDeclaration { + context_clause: vec![ + code.s1("library lib;") + .library_clause() + .map_into(ContextItem::Library), + code.s1("use lib.foo;") + .use_clause() + .map_into(ContextItem::Use), + ], + ident: code.s1("cfg").ident(), + entity_name: code.s1("entity_name").selected_name(), + decl: vec![], + vunit_bind_inds: Vec::new(), + block_config: BlockConfiguration { + block_spec: code.s1("rtl(0)").name(), + use_clauses: vec![], + items: vec![], + }, + configuration_token: code.keyword_token(Configuration, 1), + is_token: code.keyword_token(Is, 1), + end_token: code.keyword_token(End, -1), + semi_token: code.keyword_token(SemiColon, -1), + } + ))], + } + ); + } + + #[test] + fn context_clause_associated_with_package_instantiation() { + let (code, design_file) = parse_ok( + " +library lib; +use lib.foo; + +package ident is new lib.foo.bar; +", + ); + assert_eq!( + design_file, + DesignFile { + design_units: vec![AnyDesignUnit::Primary(AnyPrimaryUnit::PackageInstance( + PackageInstantiation { + context_clause: vec![ + code.s1("library lib;") + .library_clause() + .map_into(ContextItem::Library), + code.s1("use lib.foo;") + .use_clause() + .map_into(ContextItem::Use), + ], + ident: code.s1("ident").ident(), + package_name: code.s1("lib.foo.bar").selected_name(), + generic_map: None, + package_token: code.keyword_token(Package, 1), + semi_token: code.keyword_token(SemiColon, -1), + } + ))], + } + ); + } + #[test] fn warning_on_orphan_context_clause() { let code = Code::new( diff --git a/vhdl_lang/src/syntax/test.rs b/vhdl_lang/src/syntax/test.rs index 0e501635..adab2946 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) 2020, Olof Kraigher olof.kraigher@gmail.com use super::common::ParseResult; use super::concurrent_statement::parse_labeled_concurrent_statement; @@ -16,7 +16,7 @@ use super::range::{parse_discrete_range, parse_range}; use super::sequential_statement::parse_sequential_statement; use super::subprogram::{parse_signature, parse_subprogram_declaration_no_semi}; use super::subtype_indication::parse_subtype_indication; -use super::tokens::{Comment, Symbols, Token, TokenStream, Tokenizer}; +use super::tokens::{Comment, KeyWordToken, Kind, Symbols, Token, TokenStream, Tokenizer}; use super::waveform::parse_waveform; use crate::ast; use crate::ast::*; @@ -425,6 +425,29 @@ impl Code { name => panic!("Expected attribute got {:?}", name), } } + + /// Get a keyword token from the n:th occurance of kind or last occurance if + /// occurance = -1 + pub fn keyword_token(&self, kind: Kind, occurance: isize) -> KeyWordToken { + let mut count = 0; + let mut keyword_token = None; + for token in self.tokenize().iter() { + if token.kind == kind { + count += 1; + if occurance == -1 || count == occurance { + keyword_token = Some(KeyWordToken { + kind: token.kind, + pos: token.pos.clone(), + comments: token.comments.clone(), + }); + if count == occurance { + break; + } + } + } + } + keyword_token.unwrap() + } } fn substr_range(source: &Source, range: Range, substr: &str, occurence: usize) -> Range { @@ -569,6 +592,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 (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_lang/src/syntax/tokens/tokenizer.rs b/vhdl_lang/src/syntax/tokens/tokenizer.rs index 5ea9ce91..6fda403b 100644 --- a/vhdl_lang/src/syntax/tokens/tokenizer.rs +++ b/vhdl_lang/src/syntax/tokens/tokenizer.rs @@ -406,6 +406,13 @@ pub struct Token { pub comments: Option>, } +#[derive(PartialEq, Clone, Debug)] +pub struct KeyWordToken { + pub kind: Kind, + pub pos: SrcPos, + pub comments: Option>, +} + #[derive(PartialEq, Clone, Debug)] pub struct TokenComments { pub leading: Vec, @@ -433,6 +440,16 @@ impl Into for Token { } } +impl From for KeyWordToken { + fn from(token: Token) -> KeyWordToken { + KeyWordToken { + kind: token.kind, + pos: token.pos, + comments: token.comments, + } + } +} + pub fn kinds_error>(pos: T, kinds: &[Kind]) -> Diagnostic { Diagnostic::error( pos.as_ref(), diff --git a/vhdl_ls/src/document_symbol.rs b/vhdl_ls/src/document_symbol.rs new file mode 100644 index 00000000..d80ab9c4 --- /dev/null +++ b/vhdl_ls/src/document_symbol.rs @@ -0,0 +1,1504 @@ +// 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) 2020, Olof Kraigher olof.kraigher@gmail.com + +use lsp_types::{DocumentSymbol, DocumentSymbolResponse, SymbolKind, Url}; +use vhdl_lang::ast::*; +use vhdl_lang::Latin1String; +use vhdl_lang::{Source, VHDLParser}; + +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.clone(), &mut children); + push_generic_interface_list(self.generic_clause.as_ref(), &mut children); + push_port_interface_list(self.port_clause.as_ref(), &mut children); + let decl_start = if let Some(ref ports) = self.port_clause { + ports.semi_token.clone() + } else if let Some(ref generics) = self.generic_clause { + generics.semi_token.clone() + } else { + self.is_token.clone() + }; + let decl_end = if let Some(ref begin) = self.begin_token { + begin.clone() + } else { + self.end_token.clone() + }; + if !self.decl.is_empty() { + push_declarations(&decl_start, &decl_end, &self.decl, &mut children); + } + if let Some(ref begin) = self.begin_token { + push_concurrent_statement_part(begin, &self.end_token, &self.statements, &mut children); + } + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("entity")), + kind: SymbolKind::Interface, + deprecated: None, + range: lsp_types::Range { + start: to_lsp_pos( + self.context_clause + .first() + .map_or(self.entity_token.pos.start(), |first| first.pos.start()), + ), + end: to_lsp_pos(self.semi_token.pos.end()), + }, + selection_range: to_lsp_range(self.ident.pos.range()), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for ConfigurationDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(self.context_clause.clone(), &mut children); + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("configuration")), + kind: SymbolKind::Constructor, + deprecated: None, + range: lsp_types::Range { + start: to_lsp_pos( + self.context_clause + .first() + .map_or(self.configuration_token.pos.start(), |first| { + first.pos.start() + }), + ), + end: to_lsp_pos(self.semi_token.pos.end()), + }, + selection_range: to_lsp_range(self.ident.pos.range()), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for PackageDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(self.context_clause.clone(), &mut children); + push_generic_interface_list(self.generic_clause.as_ref(), &mut children); + push_declarations( + self.generic_clause + .as_ref() + .map_or(&self.is_token, |generics| &generics.semi_token), + &self.end_token, + &self.decl, + &mut children, + ); + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("package")), + kind: SymbolKind::Package, + deprecated: None, + range: lsp_types::Range { + start: to_lsp_pos( + self.context_clause + .first() + .map_or(self.package_token.pos.start(), |first| first.pos.start()), + ), + end: to_lsp_pos(self.semi_token.pos.end()), + }, + selection_range: to_lsp_range(self.ident.pos.range()), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for PackageInstantiation { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(self.context_clause.clone(), &mut children); + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("package instance")), + kind: SymbolKind::Package, + deprecated: None, + range: lsp_types::Range { + start: to_lsp_pos( + self.context_clause + .first() + .map_or(self.package_token.pos.start(), |first| first.pos.start()), + ), + end: to_lsp_pos(self.semi_token.pos.end()), + }, + selection_range: to_lsp_range(self.ident.pos.range()), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for ContextDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(self.items.clone(), &mut children); + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("context")), + kind: SymbolKind::Namespace, + deprecated: None, + range: lsp_range(&self.context_token, &self.semi_token), + selection_range: to_lsp_range(self.ident.pos.range()), + 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.clone(), &mut children); + push_declarations(&self.is_token, &self.end_token, &self.decl, &mut children); + DocumentSymbol { + name: self.ident.item.item.name_utf8(), + detail: Some(String::from("package body")), + kind: SymbolKind::Package, + deprecated: None, + range: lsp_types::Range { + start: to_lsp_pos( + self.context_clause + .first() + .map_or(self.package_token.pos.start(), |first| first.pos.start()), + ), + end: to_lsp_pos(self.semi_token.pos.end()), + }, + selection_range: to_lsp_range(self.ident.item.pos.range()), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for ArchitectureBody { + fn document_symbol(&self) -> DocumentSymbol { + let mut children = vec![]; + push_context_clause(self.context_clause.clone(), &mut children); + push_declarations(&self.is_token, &self.begin_token, &self.decl, &mut children); + push_concurrent_statement_part( + &self.begin_token, + &self.end_token, + &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: lsp_types::Range { + start: to_lsp_pos( + self.context_clause + .first() + .map_or(self.architecture_token.pos.start(), |first| { + first.pos.start() + }), + ), + end: to_lsp_pos(self.semi_token.pos.end()), + }, + selection_range: to_lsp_range(self.ident.pos.range()), + children: none_if_empty(children), + } + } +} + +impl HasDocumentSymbol for ContextClause { + fn document_symbol(&self) -> DocumentSymbol { + let (range, selection_range, children) = { + if let Some(first) = self.first() { + ( + to_lsp_range(first.pos.combine(&self.last().unwrap().pos).range()), + to_lsp_range(first.pos.range()), + { + let mut children = vec![]; + for child in self.iter() { + children.push(child.item.document_symbol()); + } + Some(children) + }, + ) + } else { + (NULL_RANGE, NULL_RANGE, None) + } + }; + DocumentSymbol { + name: String::from("context"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range, + selection_range, + children, + } + } +} + +impl HasDocumentSymbol for ContextItem { + fn document_symbol(&self) -> DocumentSymbol { + match self { + ContextItem::Use(use_clause) => use_clause.document_symbol(), + ContextItem::Library(library_clause) => library_clause.document_symbol(), + ContextItem::Context(context_reference) => context_reference.document_symbol(), + } + } +} + +impl HasDocumentSymbol for UseClause { + fn document_symbol(&self) -> DocumentSymbol { + if self.name_list.is_empty() { + DocumentSymbol { + name: String::from("use"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: NULL_RANGE, + selection_range: NULL_RANGE, + children: None, + } + } else if self.name_list.len() == 1 { + let name = self.name_list.first().unwrap(); + DocumentSymbol { + name: name_to_string(&name.item), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(name.pos.range()), + selection_range: to_lsp_range(name.pos.range()), + children: None, + } + } else { + let first = self.name_list.first().unwrap(); + let last = self.name_list.last().unwrap(); + DocumentSymbol { + name: String::from("use"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(first.pos.combine(&last.pos).range()), + selection_range: to_lsp_range(first.pos.range()), + children: Some( + self.name_list + .iter() + .map(|name| DocumentSymbol { + name: name_to_string(&name.item), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(name.pos.range()), + selection_range: to_lsp_range(name.pos.range()), + children: None, + }) + .collect(), + ), + } + } + } +} + +impl HasDocumentSymbol for LibraryClause { + fn document_symbol(&self) -> DocumentSymbol { + if self.name_list.is_empty() { + DocumentSymbol { + name: String::from("library"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: NULL_RANGE, + selection_range: NULL_RANGE, + children: None, + } + } else if self.name_list.len() == 1 { + let name = self.name_list.first().unwrap(); + DocumentSymbol { + name: name.item.name_utf8(), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(name.pos.range()), + selection_range: to_lsp_range(name.pos.range()), + children: None, + } + } else { + let first = self.name_list.first().unwrap(); + let last = self.name_list.last().unwrap(); + DocumentSymbol { + name: String::from("library"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(first.pos.combine(&last.pos).range()), + selection_range: to_lsp_range(first.pos.range()), + children: Some( + self.name_list + .iter() + .map(|name| DocumentSymbol { + name: name.item.name_utf8(), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(name.pos.range()), + selection_range: to_lsp_range(name.pos.range()), + children: None, + }) + .collect(), + ), + } + } + } +} + +impl HasDocumentSymbol for ContextReference { + fn document_symbol(&self) -> DocumentSymbol { + if self.name_list.is_empty() { + DocumentSymbol { + name: String::from("context"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: NULL_RANGE, + selection_range: NULL_RANGE, + children: None, + } + } else if self.name_list.len() == 1 { + let name = self.name_list.first().unwrap(); + DocumentSymbol { + name: name_to_string(&name.item), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(name.pos.range()), + selection_range: to_lsp_range(name.pos.range()), + children: None, + } + } else { + let first = self.name_list.first().unwrap(); + let last = self.name_list.last().unwrap(); + DocumentSymbol { + name: String::from("context"), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(first.pos.combine(&last.pos).range()), + selection_range: to_lsp_range(first.pos.range()), + children: Some( + self.name_list + .iter() + .map(|name| DocumentSymbol { + name: name_to_string(&name.item), + detail: None, + kind: SymbolKind::Namespace, + deprecated: None, + range: to_lsp_range(name.pos.range()), + selection_range: to_lsp_range(name.pos.range()), + children: None, + }) + .collect(), + ), + } + } + } +} + +impl HasDocumentSymbol for InterfaceList { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("interface list"), + detail: None, + kind: SymbolKind::Unknown, + deprecated: None, + range: lsp_range(&self.start_token, &self.semi_token), + selection_range: lsp_token_range(&self.start_token), + children: Some( + self.items + .iter() + .map(|item| item.document_symbol()) + .collect(), + ), + } + } +} + +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.range()), + selection_range: to_lsp_range(ident.pos.range()), + 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 { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: { + match self.class { + ObjectClass::Constant => None, + _ => Some(format!(": {}", mode_to_string(self.mode))), + } + }, + kind: symbol_kind_from_object_class(self.class), + deprecated: None, + range: to_lsp_range(self.ident.pos.range()), + selection_range: to_lsp_range(self.ident.pos.range()), + children: None, + } + } +} + +impl HasDocumentSymbol for InterfaceFileDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: self.ident.item.name_utf8(), + detail: Some(String::from("File")), + kind: SymbolKind::File, + deprecated: None, + range: to_lsp_range(self.ident.pos.range()), + selection_range: to_lsp_range(self.ident.pos.range()), + 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.range()), + selection_range: to_lsp_range(procedure.designator.pos.range()), + 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.range()), + selection_range: to_lsp_range(function.designator.pos.range()), + children: None, + }, + } + } +} + +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.range()), + selection_range: to_lsp_range(self.ident.pos.range()), + 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.item.document_symbol(), //WithPos + 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.range()), + selection_range: to_lsp_range(self.ident.pos.range()), + 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.range()), + selection_range: to_lsp_range(self.ident.pos.range()), + 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.range()), + selection_range: to_lsp_range(self.ident.pos.range()), + 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.range()), + selection_range: to_lsp_range(self.ident.pos.range()), + 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.range()), + selection_range: to_lsp_range(spec.ident.pos.range()), + 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.range()), + selection_range: to_lsp_range(decl.ident.pos.range()), + children: None, + }, + } + } +} + +impl HasDocumentSymbol for AliasDeclaration { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: designator_name_to_string(&self.designator.item), + detail: Some(String::from("alias")), + kind: SymbolKind::Interface, + deprecated: None, + range: to_lsp_range(self.designator.pos.range()), + selection_range: to_lsp_range(self.designator.pos.range()), + 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.range()), + selection_range: to_lsp_range(self.spec.component_name.pos.range()), + children: None, + } + } +} + +impl HasDocumentSymbol for LabeledConcurrentStatement { + fn document_symbol(&self) -> DocumentSymbol { + let mut symbol = match &self.statement { + 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(), + }; + if let Some(ref label) = self.label { + symbol.detail = Some(symbol.name); + 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.range()); + } + symbol + } +} + +impl HasDocumentSymbol for ConcurrentProcedureCall { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("procedure"), + detail: None, + kind: SymbolKind::Method, + deprecated: None, + range: to_lsp_range(self.call.name.pos.range()), + selection_range: to_lsp_range(self.call.name.pos.range()), + children: None, + } + } +} + +impl HasDocumentSymbol for BlockStatement { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("block"), + detail: None, + kind: SymbolKind::Module, + deprecated: None, + range: lsp_range(&self.block_token, &self.semi_token), + selection_range: lsp_token_range(&self.block_token), + children: None, + } + } +} + +impl HasDocumentSymbol for ProcessStatement { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("process"), + detail: None, + kind: SymbolKind::Event, + deprecated: None, + range: lsp_range(&self.start_token, &self.semi_token), + selection_range: lsp_token_range(&self.start_token), + children: None, + } + } +} + +impl HasDocumentSymbol for ConcurrentAssertStatement { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("assertion"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: to_lsp_range(self.statement.condition.pos.range()), + selection_range: to_lsp_range(self.statement.condition.pos.range()), + children: None, + } + } +} + +impl HasDocumentSymbol for ConcurrentSignalAssignment { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("assignment"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: to_lsp_range(self.target.pos.range()), + selection_range: to_lsp_range(self.target.pos.range()), + children: None, + } + } +} + +impl HasDocumentSymbol for InstantiationStatement { + fn document_symbol(&self) -> DocumentSymbol { + match &self.unit { + InstantiatedUnit::Component(selected_name) => DocumentSymbol { + name: String::from("component"), + detail: None, + kind: SymbolKind::Class, + deprecated: None, + range: to_lsp_range(selected_name.pos.range()), + selection_range: to_lsp_range(selected_name.pos.range()), + children: None, + }, + InstantiatedUnit::Entity(selected_name, _) => DocumentSymbol { + name: String::from("entity"), + detail: None, + kind: SymbolKind::Class, + deprecated: None, + range: to_lsp_range(selected_name.pos.range()), + selection_range: to_lsp_range(selected_name.pos.range()), + children: None, + }, + InstantiatedUnit::Configuration(selected_name) => DocumentSymbol { + name: String::from("configuration"), + detail: None, + kind: SymbolKind::Class, + deprecated: None, + range: to_lsp_range(selected_name.pos.range()), + selection_range: to_lsp_range(selected_name.pos.range()), + children: None, + }, + } + } +} + +impl HasDocumentSymbol for ForGenerateStatement { + fn document_symbol(&self) -> DocumentSymbol { + DocumentSymbol { + name: String::from("generate"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: to_lsp_range(self.index_name.pos.range()), + selection_range: to_lsp_range(self.index_name.pos.range()), + 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.range()) + } else { + NULL_RANGE + }; + DocumentSymbol { + name: String::from("generate"), + detail: None, + 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.range()); + DocumentSymbol { + name: String::from("generate"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range, + selection_range: range, + children: None, + } + } +} + +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 name_to_string(name: &Name) -> String { + match name { + Name::Designator(designator) => designator_name_to_string(&designator.item), + Name::Selected(name, designator) => format!( + "{}.{}", + name_to_string(&name.item), + designator_name_to_string(&designator.item.item) + ), + Name::SelectedAll(name) => format!("{}.{}", name_to_string(&name.item), "all"), + _ => String::from(" "), + } +} + +fn designator_name_to_string(designator: &Designator) -> String { + match designator { + Designator::Identifier(symbol) => symbol.name_utf8(), + Designator::OperatorSymbol(symbol) => format!("\"{}\"", symbol.to_string()), + Designator::Character(character) => { + format!("'{}'", Latin1String::new(&[character.to_owned()])) + } + } +} + +fn mode_to_string(mode: Mode) -> String { + String::from(match mode { + Mode::In => "in", + Mode::Out => "out", + Mode::InOut => "inout", + Mode::Buffer => "buffer", + Mode::Linkage => "linkage", + }) +} + +const NULL_RANGE: lsp_types::Range = lsp_types::Range { + start: lsp_types::Position { + line: 0, + character: 0, + }, + end: lsp_types::Position { + line: 0, + character: 0, + }, +}; + +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(range: vhdl_lang::Range) -> lsp_types::Range { + lsp_types::Range { + start: to_lsp_pos(range.start), + end: to_lsp_pos(range.end), + } +} + +fn lsp_range(from: &KeyWordToken, to: &KeyWordToken) -> lsp_types::Range { + lsp_types::Range { + start: to_lsp_pos(from.pos.start()), + end: to_lsp_pos(to.pos.end()), + } +} + +fn lsp_token_range(token: &KeyWordToken) -> lsp_types::Range { + lsp_types::Range { + start: to_lsp_pos(token.pos.start()), + end: to_lsp_pos(token.pos.end()), + } +} + +fn lsp_range_between(from: &KeyWordToken, to: &KeyWordToken) -> lsp_types::Range { + lsp_types::Range { + start: to_lsp_pos(from.pos.end()), + end: to_lsp_pos(to.pos.start()), + } +} + +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: ContextClause, symbols: &mut Vec) { + if !context_clause.is_empty() { + symbols.push(context_clause.document_symbol()); + } +} + +fn push_generic_interface_list( + interface_list: Option<&InterfaceList>, + symbols: &mut Vec, +) { + push_interface_list(interface_list, symbols, "generics", SymbolKind::Constant); +} + +fn push_port_interface_list( + interface_list: Option<&InterfaceList>, + symbols: &mut Vec, +) { + push_interface_list(interface_list, symbols, "ports", SymbolKind::Field); +} + +fn push_interface_list( + interface_list: Option<&InterfaceList>, + symbols: &mut Vec, + name: &str, + symbol_kind: SymbolKind, +) { + if let Some(ref list) = interface_list { + let mut symbol = list.document_symbol(); + symbol.name = String::from(name); + symbol.kind = symbol_kind; + symbols.push(symbol); + } +} + +fn push_declarations( + start_token: &KeyWordToken, + end_token: &KeyWordToken, + decl: &[Declaration], + symbols: &mut Vec, +) { + symbols.push(DocumentSymbol { + name: String::from("declarations"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: lsp_range_between(start_token, end_token), + selection_range: lsp_range_between(start_token, end_token), + children: Some(decl.iter().map(|decl| decl.document_symbol()).collect()), + }); +} + +fn push_concurrent_statement_part( + start_token: &KeyWordToken, + end_token: &KeyWordToken, + statements: &[LabeledConcurrentStatement], + symbols: &mut Vec, +) { + symbols.push(DocumentSymbol { + name: String::from("statements"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: lsp_range_between(start_token, end_token), + selection_range: lsp_range_between(start_token, end_token), + children: Some( + statements + .iter() + .map(|decl| decl.document_symbol()) + .collect(), + ), + }); +} + +#[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.len() > 0 { + 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 line_number = 0; + let mut occurance = 0; + for line in code.lines() { + if !found_start { + if let Some(pos) = line.find(start) { + occurance += 1; + if occurance == start_occurance { + start_column = pos; + if !inclusive { + start_column = 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_column + end.len(); + } + end_line = line_number; + break; + } + } + line_number += 1; + } + + 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: None, + 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: range1(code, "generic"), + children: Some(vec![DocumentSymbol { + name: String::from("g1"), + detail: None, + 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::Field, + deprecated: None, + range: range(code, "port(", ");"), + selection_range: range1(code, "port"), + children: Some(vec![DocumentSymbol { + name: String::from("p1"), + detail: Some(String::from(": in")), + 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" }; + DocumentSymbol { + name: String::from("declarations"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: find_range(code, start, start_occurance, end, false), + selection_range: find_range(code, start, start_occurance, end, false), + 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" }; + DocumentSymbol { + name: String::from("statements"), + detail: None, + kind: SymbolKind::Field, + deprecated: None, + range: range_between(code, start, "end"), + selection_range: range_between(code, start, "end"), + 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; +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, false), + ]), + } + ); + } + + #[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; +"; + 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;"), + selection_range: range1(code, "rtl"), + children: Some(vec![ + ieee_context(code), + simple_declaration(code, "is", 1, true), + simple_statement(code, true) + ]), + } + ); + } + + #[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/lib.rs b/vhdl_ls/src/lib.rs index 9284f75f..ca483f0f 100644 --- a/vhdl_ls/src/lib.rs +++ b/vhdl_ls/src/lib.rs @@ -2,11 +2,12 @@ // 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) 2020, Olof Kraigher olof.kraigher@gmail.com #[macro_use] extern crate log; +mod document_symbol; mod rpc_channel; mod stdio_server; mod vhdl_server; diff --git a/vhdl_ls/src/main.rs b/vhdl_ls/src/main.rs index 3721aa82..b0b5e218 100644 --- a/vhdl_ls/src/main.rs +++ b/vhdl_ls/src/main.rs @@ -2,13 +2,10 @@ // 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 vhdl_ls; +// Copyright (c) 2020, Olof Kraigher olof.kraigher@gmail.com #[macro_use] extern crate log; -use env_logger; fn main() { use clap::App; diff --git a/vhdl_ls/src/rpc_channel.rs b/vhdl_ls/src/rpc_channel.rs index ca3b4df8..647545a8 100644 --- a/vhdl_ls/src/rpc_channel.rs +++ b/vhdl_ls/src/rpc_channel.rs @@ -2,12 +2,11 @@ // 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) 2020, Olof Kraigher olof.kraigher@gmail.com //! Contains the RpcChannel Traid and associated convenience functions use lsp_types::*; -use serde; use vhdl_lang::{Message, MessageHandler}; pub trait RpcChannel { diff --git a/vhdl_ls/src/stdio_server.rs b/vhdl_ls/src/stdio_server.rs index b561f672..43d094c6 100644 --- a/vhdl_ls/src/stdio_server.rs +++ b/vhdl_ls/src/stdio_server.rs @@ -2,15 +2,10 @@ // 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) 2020, Olof Kraigher olof.kraigher@gmail.com -use jsonrpc_core; - -use serde; -use serde_json; - -use self::jsonrpc_core::request::Notification; -use self::jsonrpc_core::{IoHandler, Params}; +use jsonrpc_core::request::Notification; +use jsonrpc_core::{IoHandler, Params}; use std::io::prelude::*; use std::io::{self, BufRead}; @@ -85,7 +80,7 @@ pub fn start() { serde_json::to_value(value).map_err(|_| jsonrpc_core::Error::internal_error()) }); - let server = lang_server; + let server = lang_server.clone(); io.add_method("textDocument/references", move |params: Params| { let value = server .lock() @@ -94,6 +89,15 @@ pub fn start() { serde_json::to_value(value).map_err(|_| jsonrpc_core::Error::internal_error()) }); + let server = lang_server; + io.add_method("textDocument/documentSymbol", move |params: Params| { + let value = server + .lock() + .unwrap() + .text_document_document_symbol(¶ms.parse().unwrap()); + serde_json::to_value(value).map_err(|_| jsonrpc_core::Error::internal_error()) + }); + // Spawn thread to read requests from stdin spawn(move || { let stdin = io::stdin(); diff --git a/vhdl_ls/src/vhdl_server.rs b/vhdl_ls/src/vhdl_server.rs index 8b3d20ed..819540fc 100644 --- a/vhdl_ls/src/vhdl_server.rs +++ b/vhdl_ls/src/vhdl_server.rs @@ -2,19 +2,20 @@ // 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) 2020, Olof Kraigher olof.kraigher@gmail.com use lsp_types::*; -use self::fnv::FnvHashMap; -use fnv; +use fnv::FnvHashMap; use std::collections::hash_map::Entry; -use self::vhdl_lang::{Config, Diagnostic, Message, Project, Severity, Source, SrcPos}; +use crate::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}; -use vhdl_lang; +use vhdl_lang::{Config, Diagnostic, Message, Project, Severity, Source, SrcPos}; pub struct VHDLServer { rpc_channel: T, @@ -150,6 +151,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 { @@ -192,6 +201,7 @@ impl InitializedVHDLServer { capabilities.declaration_provider = Some(true); capabilities.definition_provider = Some(true); capabilities.references_provider = Some(true); + capabilities.document_symbol_provider = Some(true); let result = InitializeResult { capabilities, server_info: None, @@ -342,6 +352,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 {