~ruther/gtkwave-tcl-generator

169593ee5568e3fe180e884b3070e8fd7c404a4d — Rutherther 1 year, 7 months ago 0f5f43a + 7b15809
Merge pull request #1 from Rutherther/feat/split-logic-respect-dry

Split file_parser logic
5 files changed, 395 insertions(+), 340 deletions(-)

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

#[derive(Eq, PartialEq, Debug)]
pub enum Operation {
    UpdateContext(ContextUpdate),
    AddEmpty,
    AddSignal(String),
}

#[derive(Eq, PartialEq, Debug)]
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.trim()))
            .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 +30 -116
@@ 1,47 1,13 @@
use std::{slice::Iter, fmt::Display};
use std::fmt::Display;

#[derive(Eq, PartialEq, Clone)]
pub enum DisplayElement {
    Signal(Signal),
    Empty(Vec<DisplayOption>)
}

#[derive(Eq, PartialEq, Clone, Copy)]
#[derive(Eq, PartialEq, Clone, Copy, Debug)]
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>,
    Omit,
}

#[derive(Eq, PartialEq, Clone)]
pub struct Architecture {
    name: String,
    entity_name: String,
    signals: Vec<Signal>
}

#[derive(Eq, PartialEq, Copy, Clone)]
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum DisplayColor {
    Normal,
    Red,


@@ 56,21 22,25 @@ 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",
            }
        )
    }
}

#[derive(Eq, PartialEq, Copy, Clone)]
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum DisplayFormat {
    Hex,
    Decimal,


@@ 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 +133 -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::{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);
            }
    pub fn signal_type(&self) -> &str {
        &self.signal_type
    }

            self.find_next_entity()
        }
    pub fn comment(&self) -> Option<&str> {
        self.comment.as_deref()
    }
}

    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);
        };
#[derive(PartialEq, Eq)]
pub enum ParsedArchitecturePart {
    Signal(ParsedSignal),
    Comment(String),
}

        if entity.architecture().is_some() {
            return Ok(entity);
        }
pub struct FileParser {}

        if found_entity.architecture().is_some() {
            return Ok(found_entity.clone());
        }

        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,41 @@ 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);
        stream.skip_until(|k| k == Kind::Architecture)?;
        stream.skip_until(|k| k == Kind::SemiColon)?;

        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 185,37 @@ 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: signal_type.clone(),
                comment: comment.clone(),
            });
        }

        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))

        stream.skip_until(|k| k == Kind::Entity)?;
        stream.skip_until(|k| k == Kind::SemiColon)?;

        Ok(ParsedEntity {
            name,
            architecture: None,
        })
    }

    fn parse_identifier(stream: &mut TokenStream) -> Result<String, ParseError> {


@@ 218,37 226,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/main.rs => src/main.rs +150 -36
@@ 1,16 1,19 @@
pub mod file_parser;
pub mod comment_parser;
pub mod display_elements;
pub mod file_parser;
pub mod tcl_generator;

use std::{path::PathBuf, fs::File, io::Write};
use std::{fs::File, io::Write, path::PathBuf};

use clap::{arg, Parser};
use file_parser::FileParser;
use comment_parser::{CommentParser, ContextUpdate, Operation};
use display_elements::DisplayFormat;
use file_parser::{FileParser, ParsedArchitecturePart, ParsedEntity};
use glob::glob;
use tcl_generator::TclGenerator;
use vhdl_lang::{syntax::Symbols, Source};

use crate::display_elements::{Signal, DisplayOption, DisplayColor};
use crate::display_elements::DisplayColor;

#[derive(Parser)]
#[command(author, version, about, long_about = None)]


@@ 26,7 29,6 @@ struct Cli {
fn main() {
    let cli = Cli::parse();
    let find_wildcard = cli.folder.to_str().unwrap().to_owned() + "/**/*.vhd";
    println!("Going to look into {find_wildcard}");

    let matching_files = glob(&find_wildcard).unwrap();



@@ 38,52 40,164 @@ fn main() {

        let source = Source::from_latin1_file(file.as_path()).unwrap();
        let contents = source.contents();
        let mut parser = FileParser::new(&source, &contents, &symbols);
        let entities = FileParser::parse_file(&source, &contents, &symbols).unwrap();

        match parser.find_next_entity() {
            Ok(entity) =>  {
                if entity.name() != &cli.testbench[..] {
                    continue;
                }
                found = true;
        for entity in entities {
            if entity.name() != cli.testbench {
                continue;
            }
            found = true;
            println!("Found the testbench.");

                println!("Found the testbench.");
            let tcl = generate_tcl(entity);

                let entity = parser.parse_entity_architecture(entity).unwrap();
                let architecture = entity.architecture().unwrap();
            let mut file = File::create(cli.output.clone()).unwrap();
            file.write_all(tcl.as_bytes()).unwrap();

                let mut generator = TclGenerator::new("top.".to_owned() + &cli.testbench + ".");
                generator.add_signal(&Signal::new("clk".to_owned(), vec![DisplayOption::Color(DisplayColor::Indigo)]))
                        .add_signal(&Signal::new("rst".to_owned(), vec![DisplayOption::Color(DisplayColor::Indigo)]))
                        .add_empty()
                        .zoom_out();
            println!("Generated {}.", cli.output.display());

                for signal in architecture.signals() {
                    if signal.name() == "clk" || signal.name() == "rst" {
                        continue;
                    }
            break;
        }

        if found {
            break;
        }
    }

                    generator.add_signal(signal);
    if !found {
        println!("Could not find the entity.")
    }
}

#[derive(Eq, PartialEq, Clone)]
struct Context {
    color: Option<DisplayColor>,
    format: Option<DisplayFormat>,
    omit: bool,
}

impl Context {
    pub fn update<'a>(&mut self, updates: impl Iterator<Item = &'a ContextUpdate>) {
        for update in updates {
            match update {
                ContextUpdate::Reset => {
                    self.color = None;
                    self.format = None;
                    self.omit = false;
                }
                ContextUpdate::SetOmit(omit) => {
                    self.omit = omit.clone();
                }
                ContextUpdate::UpdateColor(color) => {
                    self.color = color.clone();
                }
                ContextUpdate::UpdateFormat(format) => {
                    self.format = Some(format.clone());
                }
            }
        }
    }

    pub fn fork<'a>(&self, updates: impl Iterator<Item = &'a ContextUpdate>) -> Context {
        let mut clone = self.clone();
        clone.update(updates);

                let generated = generator.generate();
        clone
    }

                let mut file = File::create(&cli.output).unwrap();
                file.write_all(generated.as_bytes()).unwrap();
    pub fn decompose(&self) -> (Option<DisplayColor>, Option<DisplayFormat>, bool) {
        (self.color, self.format, self.omit)
    }
}

                break;
fn generate_tcl(entity: ParsedEntity) -> String {
    let architecture = entity.architecture().unwrap();

    let mut generator = TclGenerator::new("top.".to_owned() + entity.name() + ".");

    let mut context = Context {
        color: None,
        format: None,
        omit: false,
    };

    for part in architecture.parts() {
        match part {
            ParsedArchitecturePart::Comment(comment) => {
                let operations = CommentParser::parse_comment(&comment[..]);
                if let Some(operation) = operations.first() {
                    if let Operation::AddSignal(signal) = operation {
                        let context_operations = operations.iter().skip(1);
                        add_signal(
                            &mut generator,
                            signal.clone(),
                            None,
                            Some(context_operations),
                            &context,
                            true
                        );
                    } else {
                        let updates = operations.iter().filter_map(|op| match op {
                            Operation::UpdateContext(update) => Some(update),
                            Operation::AddEmpty => {
                                generator.add_empty();
                                None
                            }
                            _ => panic!(),
                        });
                        context.update(updates);
                    }
                }
            },
            Err(err) => {
                println!("{:?}", err);
            ParsedArchitecturePart::Signal(signal) => {
                let mut operations = None;

                if let Some(comment) = signal.comment() {
                    operations = Some(CommentParser::parse_comment(comment));
                }

                add_signal(
                    &mut generator,
                    signal.name().to_owned(),
                    Some(signal.signal_type()),
                    operations.as_ref().map(|x| x.iter()),
                    &context,
                    false
                );
            }
        }
    }

        if found {
            break;
        }
    generator.generate()
}

fn add_signal<'a>(
    generator: &mut TclGenerator,
    signal: String,
    signal_type: Option<&str>,
    operations: Option<impl Iterator<Item = &'a Operation>>,
    context: &Context,
    override_omit: bool
) {
    let (color, mut format, omit) = if let Some(operations) = operations {
        let updates = operations.into_iter().filter_map(|op| {
            if let Operation::UpdateContext(update) = op {
                Some(update)
            } else {
                None
            }
        });

        context.fork(updates).decompose()
    } else {
        context.decompose()
    };

    if format.is_none() && signal_type.is_some() && signal_type.unwrap() == "std_logic_vector" {
        format = Some(DisplayFormat::Binary);
    }

    if !found {
        println!("Could not find the entity.")
    if override_omit || !omit {
        generator.add_signal(signal, color, format);
    }
}

M src/tcl_generator.rs => src/tcl_generator.rs +9 -30
@@ 1,11 1,9 @@
use string_builder::Builder;

use crate::display_elements::{DisplayColor, DisplayFormat, Signal, DisplayOption};

use crate::display_elements::{DisplayColor, DisplayFormat};

pub struct TclGenerator {
    signals: Vec<(String, Option<DisplayColor>, Option<DisplayFormat>)>,
    zoom_out: bool,
    signal_prefix: String,
}



@@ 13,28 11,17 @@ 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 30,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 38,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 65,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