From a2d2abb06f308b7590ba4af679a80ce5d19c0f51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Franti=C5=A1ek=20Boh=C3=A1=C4=8Dek?= Date: Sat, 2 Sep 2023 00:12:27 +0200 Subject: [PATCH] feat: split logic of context and comment tokens Respect DRY for file_parser. So far, the file parser did not just parse the file, it also parsed custom comment "tokens" and managed a context. This is not the responsibility of the parser. The parser should just parse the file and return the parsed parts. --- src/comment_parser.rs | 71 ++++++++++ src/display_elements.rs | 140 ++++---------------- src/file_parser.rs | 284 ++++++++++++++++++---------------------- src/tcl_generator.rs | 34 +---- 4 files changed, 228 insertions(+), 301 deletions(-) create mode 100644 src/comment_parser.rs diff --git a/src/comment_parser.rs b/src/comment_parser.rs new file mode 100644 index 0000000000000000000000000000000000000000..1832b92244d1457922123629d89321b205c72c3d --- /dev/null +++ b/src/comment_parser.rs @@ -0,0 +1,71 @@ +use crate::display_elements::{DisplayColor, DisplayFormat}; + +pub enum Operation { + UpdateContext(ContextUpdate), + AddEmpty, + AddSignal(String), +} + +pub enum ContextUpdate { + UpdateColor(Option), + UpdateFormat(DisplayFormat), + SetOmit(bool), + Reset, +} + +pub struct CommentParser {} + +impl CommentParser { + pub fn parse_comment(comment: &str) -> Vec { + comment + .split(['\n', ','].as_ref()) + .map(|token| Self::parse_token(token)) + .filter(|operation| operation.is_some()) + .into_iter() + .map(|operation| operation.unwrap()) + .collect() + } + + fn parse_token(token: &str) -> Option { + Some(match token { + "omit" => Operation::UpdateContext(ContextUpdate::SetOmit(true)), + "empty" => Operation::AddEmpty, + "reset" => Operation::UpdateContext(ContextUpdate::Reset), + _ if token.starts_with("add signal ") => Operation::AddSignal( + token["add signal ".len()..].to_owned(), + ), + _ if token.starts_with("format ") => Operation::UpdateContext( + ContextUpdate::UpdateFormat(Self::parse_format(&token["format ".len()..])), + ), + _ if token.starts_with("color ") => Operation::UpdateContext( + ContextUpdate::UpdateColor(Self::parse_color(&token["color ".len()..])), + ), + _ => return None, + }) + } + + fn parse_format(format: &str) -> DisplayFormat { + match format { + "hex" => DisplayFormat::Hex, + "decimal" => DisplayFormat::Decimal, + "signed decimal" => DisplayFormat::SignedDecimal, + "binary" => DisplayFormat::Binary, + _ => DisplayFormat::Decimal, + } + } + + fn parse_color(color: &str) -> Option { + Some(match color { + "normal" => DisplayColor::Normal, + "red" => DisplayColor::Red, + "orange" => DisplayColor::Orange, + "yellow" => DisplayColor::Yellow, + "green" => DisplayColor::Green, + "blue" => DisplayColor::Blue, + "Indigo" => DisplayColor::Indigo, + "Violet" => DisplayColor::Violet, + "Cycle" => DisplayColor::Cycle, + _ => return None, + }) + } +} diff --git a/src/display_elements.rs b/src/display_elements.rs index dd94ee5bd69f7242bb4a16934932c3e40bdf07d7..08e0cab8ba75889374c09e201c1736f2f216c5f1 100644 --- a/src/display_elements.rs +++ b/src/display_elements.rs @@ -1,44 +1,10 @@ -use std::{slice::Iter, fmt::Display}; - -#[derive(Eq, PartialEq, Clone)] -pub enum DisplayElement { - Signal(Signal), - Empty(Vec) -} +use std::{fmt::Display, slice::Iter}; #[derive(Eq, PartialEq, Clone, Copy)] pub enum DisplayOption { Color(DisplayColor), Format(DisplayFormat), - Omit -} - -#[derive(Eq, PartialEq, Clone)] -pub struct Signal { - name: String, - options: Vec -} - -impl From<&str> for Signal { - fn from(value: &str) -> Self { - Self { - name: value.to_owned(), - options: vec![] - } - } -} - -#[derive(Eq, PartialEq, Clone)] -pub struct Entity { - name: String, - architecture: Option, -} - -#[derive(Eq, PartialEq, Clone)] -pub struct Architecture { - name: String, - entity_name: String, - signals: Vec + Omit, } #[derive(Eq, PartialEq, Copy, Clone)] @@ -56,17 +22,21 @@ pub enum DisplayColor { impl Display for DisplayColor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", match self { - DisplayColor::Normal => "Normal", - DisplayColor::Red => "Red", - DisplayColor::Orange => "Orange", - DisplayColor::Yellow => "Yellow", - DisplayColor::Green => "Green", - DisplayColor::Blue => "Blue", - DisplayColor::Indigo => "Indigo", - DisplayColor::Violet => "Violet", - DisplayColor::Cycle => "Cycle", - }) + write!( + f, + "{}", + match self { + DisplayColor::Normal => "Normal", + DisplayColor::Red => "Red", + DisplayColor::Orange => "Orange", + DisplayColor::Yellow => "Yellow", + DisplayColor::Green => "Green", + DisplayColor::Blue => "Blue", + DisplayColor::Indigo => "Indigo", + DisplayColor::Violet => "Violet", + DisplayColor::Cycle => "Cycle", + } + ) } } @@ -80,71 +50,15 @@ pub enum DisplayFormat { impl Display for DisplayFormat { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", match self { - DisplayFormat::Hex => "Hex", - DisplayFormat::Decimal => "Decimal", - DisplayFormat::SignedDecimal => "SignedDecimal", - DisplayFormat::Binary => "Binary", - }) - } -} - -impl Signal { - pub fn new(name: String, options: Vec) -> Self { - Self { - name, - options - } - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn options(&self) -> Iter<'_, DisplayOption> { - self.options.iter() - } -} - -impl Entity { - pub fn new(name: String) -> Self { - Self { - name, - architecture: None - } - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn add_architecture(&mut self, architecture: Architecture) { - self.architecture = Some(architecture); - } - - pub fn architecture(&self) -> Option<&Architecture> { - self.architecture.as_ref() - } -} - -impl Architecture { - pub fn new(name: String, entity_name: String, signals: Vec) -> Self { - Self { - name, - entity_name, - signals, - } - } - - pub fn signals(&self) -> Iter<'_, Signal> { - self.signals.iter() - } - - pub fn name(&self) -> &str { - &self.name - } - - pub fn entity_name(&self) -> &str { - &self.entity_name + write!( + f, + "{}", + match self { + DisplayFormat::Hex => "Hex", + DisplayFormat::Decimal => "Decimal", + DisplayFormat::SignedDecimal => "SignedDecimal", + DisplayFormat::Binary => "Binary", + } + ) } } diff --git a/src/file_parser.rs b/src/file_parser.rs index bbf026cae0b7a4457edccadc5183e8363480e511..99b55f6cf2473964dd28da0014435c9e8fd9a4d6 100644 --- a/src/file_parser.rs +++ b/src/file_parser.rs @@ -1,131 +1,128 @@ -use vhdl_lang::{Source, syntax::{tokens::{TokenStream, Tokenizer, Kind, Comment}, Symbols}, Diagnostic, data::{Contents, ContentReader}}; - -use crate::display_elements::{DisplayColor, Entity, Architecture, Signal, DisplayOption, DisplayFormat}; - -#[derive(PartialEq, Eq, Clone)] -struct Context { - color: Option, - omit: bool +use vhdl_lang::{ + data::{ContentReader, Contents}, + syntax::{ + tokens::{Comment, Kind, TokenStream, Tokenizer}, + Symbols, + }, + Diagnostic, Source, +}; + +#[derive(Eq, PartialEq)] +pub struct ParsedEntity { + name: String, + architecture: Option, } -pub struct FileParser<'a> { - diagnostics: Vec, - stream: TokenStream<'a>, +impl ParsedEntity { + pub fn name(&self) -> &str { + &self.name[..] + } - entities: Vec, - current_entity: usize + pub fn architecture(&self) -> Option<&ParsedArchitecture> { + self.architecture.as_ref() + } } -#[derive(Debug)] -pub enum ParseError { - EntityNotPresent, - ArchitectureNotFound, - ParsingError(Diagnostic), - EndOfFile, +#[derive(Eq, PartialEq)] +pub struct ParsedArchitecture { + name: String, + entity_name: String, + parts: Vec, } -impl From for ParseError { - fn from(value: Diagnostic) -> Self { - Self::ParsingError(value) +impl ParsedArchitecture { + pub fn name(&self) -> &str { + &self.name } -} -impl From<&Context> for Vec { - fn from(value: &Context) -> Self { - let mut options = vec![]; - - if value.omit { - options.push(DisplayOption::Omit); - } - - if let Some(color) = value.color { - options.push(DisplayOption::Color(color)); - } + pub fn entity_name(&self) -> &str { + &self.entity_name + } - options + pub fn parts(&self) -> &Vec { + &self.parts } } -impl<'a> FileParser<'a> { - pub fn new(source: &'a Source, contents: &'a Contents, symbols: &'a Symbols) -> Self { - let mut diagnostics = vec![]; - let tokenizer = Tokenizer::new(symbols, source, ContentReader::new(contents)); - let stream = TokenStream::new(tokenizer, &mut diagnostics); +#[derive(PartialEq, Eq)] +pub struct ParsedSignal { + name: String, + signal_type: String, + comment: Option, +} - Self { - diagnostics, - stream, - entities: vec![], - current_entity: 0, - } +impl ParsedSignal { + pub fn name(&self) -> &str { + &self.name } - pub fn find_next_entity(&mut self) -> Result { - if self.current_entity < self.entities.len() { - self.current_entity += 1; - return Ok(self.entities[self.current_entity - 1].clone()); - } - - if self.stream.skip_until(|k| k == Kind::Entity || k == Kind::Architecture).is_err() { - return Err(ParseError::EndOfFile); - } - - if self.stream.peek_kind().unwrap() == Kind::Entity { - let entity = Self::parse_entity(&mut self.stream)?; - self.entities.push(entity.clone()); - self.current_entity += 1; - - Ok(entity) - } else { - let architecture = Self::parse_architecture(&mut self.stream)?; - - if let Some(entity) = self.entities.iter_mut().find(|e| e.name() == architecture.entity_name()) { - entity.add_architecture(architecture); - } - - self.find_next_entity() - } + pub fn signal_type(&self) -> &str { + &self.signal_type } - pub fn parse_entity_architecture(&mut self, mut entity: Entity) -> Result { - let Some(found_entity) = self.entities.iter_mut().find(|e| e.name() == entity.name()) else { - return Err(ParseError::EntityNotPresent); - }; + pub fn comment(&self) -> Option<&str> { + self.comment.as_deref() + } +} - if entity.architecture().is_some() { - return Ok(entity); - } +#[derive(PartialEq, Eq)] +pub enum ParsedArchitecturePart { + Signal(ParsedSignal), + Comment(String), +} - if found_entity.architecture().is_some() { - return Ok(found_entity.clone()); - } +pub struct FileParser {} - if self.stream.skip_until(|k| k == Kind::Entity || k == Kind::Architecture).is_err() { - return Err(ParseError::EndOfFile); - } +#[derive(Debug)] +pub enum ParseError { + ArchitectureWithoutEntity, + ParsingError(Diagnostic), + EndOfFile, +} - if self.stream.peek_kind().unwrap() == Kind::Entity { - let entity = Self::parse_entity(&mut self.stream)?; - self.entities.push(entity.clone()); - return self.parse_entity_architecture(entity) - } +impl From for ParseError { + fn from(value: Diagnostic) -> Self { + Self::ParsingError(value) + } +} - let architecture = Self::parse_architecture(&mut self.stream)?; - if architecture.entity_name() == entity.name() { - entity.add_architecture(architecture.clone()); - found_entity.add_architecture(architecture); +impl FileParser { + pub fn parse_file( + source: &Source, + contents: &Contents, + symbols: &Symbols, + ) -> Result, ParseError> { + let mut entities = vec![]; + let mut diagnostics = vec![]; + let tokenizer = Tokenizer::new(symbols, source, ContentReader::new(contents)); + let mut stream = TokenStream::new(tokenizer, &mut diagnostics); - return Ok(entity); - } + while stream + .skip_until(|k| k == Kind::Entity || k == Kind::Architecture) + .is_ok() + { + let kind = stream.peek_kind().unwrap(); - if let Some(matched_entity) = self.entities.iter_mut().find(|e| e.name() == architecture.entity_name()) { - matched_entity.add_architecture(architecture); + match kind { + Kind::Entity => { + entities.push(Self::parse_entity(&mut stream)?); + } + Kind::Architecture => { + let architecture = Self::parse_architecture(&mut stream)?; + let entity = entities + .iter_mut() + .find(|e| e.name == architecture.entity_name) + .ok_or(ParseError::ArchitectureWithoutEntity)?; + entity.architecture = Some(architecture); + } + _ => panic!("Wrong kind. Skip until bugged."), + } } - self.parse_entity_architecture(entity) + Ok(entities) } - fn parse_architecture(stream: &mut TokenStream) -> Result { + fn parse_architecture(stream: &mut TokenStream) -> Result { stream.expect_kind(Kind::Architecture)?; let architecture_name = Self::parse_identifier(stream)?; @@ -136,31 +133,38 @@ impl<'a> FileParser<'a> { stream.expect_kind(Kind::Is)?; - let mut context = Context { color: None, omit: false }; - let mut signals = vec![]; + let mut parts: Vec = vec![]; - while !stream.next_kind_is(Kind::Begin) { + loop { let token = stream.peek().ok_or(ParseError::EndOfFile)?; if let Some(comments) = &token.comments { for comment in &comments.leading { - Self::update_context(&mut context, comment); + parts.push(ParsedArchitecturePart::Comment(comment.value.clone())); } } match token.kind { - Kind::Signal => signals.append(Self::parse_signals(stream, &context)?.as_mut()), + Kind::Signal => parts.extend( + Self::parse_signals(stream)? + .into_iter() + .map(|s| ParsedArchitecturePart::Signal(s)), + ), Kind::Begin => break, _ => stream.skip(), } } - let architecture = Architecture::new(architecture_name, entity_name, signals); + let architecture = ParsedArchitecture { + name: architecture_name, + entity_name, + parts, + }; Ok(architecture) } - fn parse_signals(stream: &mut TokenStream, context: &Context) -> Result, ParseError> { + fn parse_signals(stream: &mut TokenStream) -> Result, ParseError> { stream.expect_kind(Kind::Signal)?; let mut signal_names = vec![]; @@ -178,36 +182,33 @@ impl<'a> FileParser<'a> { stream.skip_until(|k| k == Kind::SemiColon)?; let semicolon_token = stream.peek().ok_or(ParseError::EndOfFile)?; - let options: Vec = if let Some(comments) = &semicolon_token.comments { - if let Some(trailing) = &comments.trailing { - let mut context = context.clone(); - Self::update_context(&mut context, trailing); - (&context).into() - } else { - context.into() - } - } else { - context.into() - }; + let comment = semicolon_token.comments.as_ref().and_then(|comments| { + comments + .trailing + .as_ref() + .map(|trailing| trailing.value.clone()) + }); let mut signals = vec![]; for signal_name in signal_names { - let mut options = options.clone(); - if signal_type.starts_with("std_logic_vector") { - options.push(DisplayOption::Format(DisplayFormat::Binary)); - } - - signals.push(Signal::new(signal_name, options)); + signals.push(ParsedSignal { + name: signal_name, + signal_type, + comment, + }); } Ok(signals) } - fn parse_entity(stream: &mut TokenStream) -> Result { + fn parse_entity(stream: &mut TokenStream) -> Result { stream.expect_kind(Kind::Entity)?; let name = Self::parse_identifier(stream)?; - Ok(Entity::new(name)) + Ok(ParsedEntity { + name, + architecture: None, + }) } fn parse_identifier(stream: &mut TokenStream) -> Result { @@ -218,37 +219,4 @@ impl<'a> FileParser<'a> { Ok(identifier.item.name_utf8()) } - - fn update_context(context: &mut Context, comment: &Comment) { - let commands = comment.value.split(['\n', ','].as_ref()); - - for command in commands { - match command.trim() { - "omit" => context.omit = true, - "reset" => { - context.color = None; - context.omit = false; - }, - _ if command.trim().starts_with("color ") => { - let color = command["color ".len()..].trim(); - - let color = match color { - "normal" => DisplayColor::Normal, - "red" => DisplayColor::Red, - "orange" => DisplayColor::Orange, - "yellow" => DisplayColor::Yellow, - "green" => DisplayColor::Green, - "blue" => DisplayColor::Blue, - "Indigo" => DisplayColor::Indigo, - "Violet" => DisplayColor::Violet, - "Cycle" => DisplayColor::Cycle, - _ => DisplayColor::Normal, - }; - - context.color = Some(color); - }, - _ => () - } - } - } } diff --git a/src/tcl_generator.rs b/src/tcl_generator.rs index 07fdb76c4da5cf76653358a2391a0561a16ff88e..c1aa5b84759c39d284663508d674140aa69da151 100644 --- a/src/tcl_generator.rs +++ b/src/tcl_generator.rs @@ -1,11 +1,9 @@ use string_builder::Builder; -use crate::display_elements::{DisplayColor, DisplayFormat, Signal, DisplayOption}; - +use crate::display_elements::{DisplayColor, DisplayFormat, DisplayOption}; pub struct TclGenerator { signals: Vec<(String, Option, Option)>, - zoom_out: bool, signal_prefix: String, } @@ -13,28 +11,12 @@ impl TclGenerator { pub fn new(signal_prefix: String) -> Self { Self { signals: vec![], - zoom_out: false, signal_prefix, } } - pub fn add_signal(&mut self, signal: &Signal) -> &mut Self { - let mut color = None; - let mut format = None; - - for option in signal.options() { - match option { - DisplayOption::Omit => return self, - DisplayOption::Color(c) => { - color = Some(c.clone()); - }, - DisplayOption::Format(f) => { - format = Some(f.clone()); - } - } - } - - self.signals.push((signal.name().to_owned(), color, format)); + pub fn add_signal(&mut self, signal: String, color: Option, format: Option) -> &mut Self { + self.signals.push((signal, color, format)); self } @@ -43,11 +25,6 @@ impl TclGenerator { self } - pub fn zoom_out(&mut self) -> &mut Self { - self.zoom_out = true; - self - } - pub fn generate(self) -> String { let mut builder = Builder::new(300); @@ -56,7 +33,6 @@ impl TclGenerator { builder.append("gtkwave::/View/Show_Filled_High_Values 1\n"); builder.append("gtkwave::/View/Show_Wave_Highlight 1\n"); builder.append("gtkwave::/View/Show_Mouseover 1\n"); - builder.append("gtkwave::/View/Left_Justified_Signals 1\n"); for signal in self.signals { if signal.0 == "" { @@ -84,9 +60,7 @@ impl TclGenerator { } } - if self.zoom_out { - builder.append("gtkwave::/Time/Zoom/Zoom_Best_Fit\n"); - } + builder.append("gtkwave::/Time/Zoom/Zoom_Best_Fit\n"); builder.string().unwrap() }