~ruther/gtkwave-tcl-generator

a2d2abb06f308b7590ba4af679a80ce5d19c0f51 — František Boháček 1 year, 7 months ago 0f5f43a
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.
4 files changed, 228 insertions(+), 301 deletions(-)

A src/comment_parser.rs
M src/display_elements.rs
M src/file_parser.rs
M src/tcl_generator.rs
A src/comment_parser.rs => src/comment_parser.rs +71 -0
@@ 0,0 1,71 @@
use crate::display_elements::{DisplayColor, DisplayFormat};

pub enum Operation {
    UpdateContext(ContextUpdate),
    AddEmpty,
    AddSignal(String),
}

pub enum ContextUpdate {
    UpdateColor(Option<DisplayColor>),
    UpdateFormat(DisplayFormat),
    SetOmit(bool),
    Reset,
}

pub struct CommentParser {}

impl CommentParser {
    pub fn parse_comment(comment: &str) -> Vec<Operation> {
        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<Operation> {
        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<DisplayColor> {
        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,
        })
   }
}

M src/display_elements.rs => src/display_elements.rs +27 -113
@@ 1,44 1,10 @@
use std::{slice::Iter, fmt::Display};

#[derive(Eq, PartialEq, Clone)]
pub enum DisplayElement {
    Signal(Signal),
    Empty(Vec<DisplayOption>)
}
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<DisplayOption>
}

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<Architecture>,
}

#[derive(Eq, PartialEq, Clone)]
pub struct Architecture {
    name: String,
    entity_name: String,
    signals: Vec<Signal>
    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<DisplayOption>) -> 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<Signal>) -> 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",
            }
        )
    }
}

M src/file_parser.rs => src/file_parser.rs +126 -158
@@ 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<DisplayColor>,
    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<ParsedArchitecture>,
}

pub struct FileParser<'a> {
    diagnostics: Vec<Diagnostic>,
    stream: TokenStream<'a>,
impl ParsedEntity {
    pub fn name(&self) -> &str {
        &self.name[..]
    }

    entities: Vec<Entity>,
    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<ParsedArchitecturePart>,
}

impl From<Diagnostic> for ParseError {
    fn from(value: Diagnostic) -> Self {
        Self::ParsingError(value)
impl ParsedArchitecture {
    pub fn name(&self) -> &str {
        &self.name
    }
}

impl From<&Context> for Vec<DisplayOption> {
    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<ParsedArchitecturePart> {
        &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<String>,
}

        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<Entity, ParseError> {
        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<Entity, ParseError> {
        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<Diagnostic> 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<Vec<ParsedEntity>, 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<Architecture, ParseError> {
    fn parse_architecture(stream: &mut TokenStream) -> Result<ParsedArchitecture, ParseError> {
        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<ParsedArchitecturePart> = 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<Vec<Signal>, ParseError> {
    fn parse_signals(stream: &mut TokenStream) -> Result<Vec<ParsedSignal>, 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<DisplayOption> = 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<Entity, ParseError> {
    fn parse_entity(stream: &mut TokenStream) -> Result<ParsedEntity, ParseError> {
        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<String, ParseError> {


@@ 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);
                },
                _ => ()
            }
        }
    }
}

M src/tcl_generator.rs => src/tcl_generator.rs +4 -30
@@ 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<DisplayColor>, Option<DisplayFormat>)>,
    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<DisplayColor>, format: Option<DisplayFormat>) -> &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()
    }

Do not follow this link