vcd_rust 0.0.1

A value change dump parser for the Rust programming language
Documentation
use crate::error::LoadError;
use crate::types::scope::Scope;
use std::str::FromStr;
use strum_macros::EnumString;

#[derive(Debug, Clone, Eq, PartialEq, EnumString)]
pub enum VarType {
    #[strum(serialize = "event")]
    Event,
    #[strum(serialize = "integer")]
    Integer,
    #[strum(serialize = "parameter")]
    Parameter,
    #[strum(serialize = "real")]
    Real,
    #[strum(serialize = "reg")]
    Reg,
    #[strum(serialize = "supply0")]
    Supply0,
    #[strum(serialize = "supply1")]
    Supply1,
    #[strum(serialize = "time")]
    Time,
    #[strum(serialize = "tri")]
    Tri,
    #[strum(serialize = "triand")]
    TriAnd,
    #[strum(serialize = "trior")]
    TriOr,
    #[strum(serialize = "trireg")]
    TriReg,
    #[strum(serialize = "tri0")]
    Tri0,
    #[strum(serialize = "tri1")]
    Tri1,
    #[strum(serialize = "wand")]
    WAnd,
    #[strum(serialize = "wire")]
    Wire,
    #[strum(serialize = "wor")]
    WOr,
}

#[derive(Debug, Clone, Eq, PartialEq)]
enum BuildState {
    VarType,
    Size,
    Identifier,
    Reference,
    Done,
}

impl BuildState {
    fn next(&self, line_num: usize) -> Result<Self, LoadError> {
        use BuildState::*;
        match *self {
            VarType => Ok(Size),
            Size => Ok(Identifier),
            Identifier => Ok(Reference),
            Reference => Ok(Done),
            Done => Err(LoadError::TooManyParameters {
                line: line_num,
                command: "var".to_string(),
            }),
        }
    }
}

#[derive(Debug, Clone, Builder)]
pub struct Variable {
    pub scope: Vec<Scope>,
    pub var_type: VarType,
    pub bit_width: usize,
    pub ascii_identifier: String,
    pub reference: String,

    #[builder(default = "BuildState::VarType", setter(skip))]
    state: BuildState,
}

impl Variable {
    pub fn new() -> Variable {
        Variable {
            scope: vec![],
            var_type: VarType::Event,
            bit_width: 0,
            ascii_identifier: "".to_string(),
            reference: "".to_string(),
            state: BuildState::VarType,
        }
    }

    pub fn append(&mut self, word: &str, line_num: usize) -> Result<(), LoadError> {
        match self.state {
            BuildState::VarType => self.write_var_type(word, line_num)?,
            BuildState::Size => self.write_bit_width(word, line_num)?,
            BuildState::Identifier => self.ascii_identifier = word.to_string(),
            BuildState::Reference => self.reference = word.to_string(),
            _ => {}
        }
        self.state = self.state.next(line_num)?;
        Ok(())
    }

    pub fn is_done(&self) -> bool {
        self.state == BuildState::Done
    }

    fn write_var_type(&mut self, word: &str, line_num: usize) -> Result<(), LoadError> {
        self.var_type = match VarType::from_str(word) {
            Ok(var_type) => var_type,
            Err(_) => {
                return Err(LoadError::InvalidParameterForCommand {
                    line: line_num,
                    command: "$var".to_string(),
                    parameter: word.to_string(),
                })
            }
        };
        Ok(())
    }

    fn write_bit_width(&mut self, word: &str, line_num: usize) -> Result<(), LoadError> {
        self.bit_width = match word.parse::<usize>() {
            Ok(bit_width) => bit_width,
            Err(_) => {
                return Err(LoadError::InvalidParameterForCommand {
                    line: line_num,
                    command: "$var".to_string(),
                    parameter: word.to_string(),
                })
            }
        };
        Ok(())
    }
}

impl PartialEq for Variable {
    fn eq(&self, other: &Self) -> bool {
        return self.scope == other.scope
            && self.var_type == other.var_type
            && self.bit_width == other.bit_width
            && self.ascii_identifier == other.ascii_identifier
            && self.reference == other.reference;
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn build_variable_1() {
        let exp_var = VariableBuilder::default()
            .scope(vec![])
            .var_type(VarType::Wire)
            .bit_width(8)
            .ascii_identifier("#".to_string())
            .reference("data".to_string())
            .build()
            .unwrap();
        let mut act_var = Variable::new();
        for word in vec!["wire", "8", "#", "data"] {
            act_var.append(word, 0).unwrap();
        }
        assert_eq!(exp_var, act_var);
        assert!(act_var.is_done());
    }

    #[test]
    fn build_variable_2() {
        let exp_var = VariableBuilder::default()
            .scope(vec![])
            .var_type(VarType::TriReg)
            .bit_width(4)
            .ascii_identifier("e".to_string())
            .reference("my_reference".to_string())
            .build()
            .unwrap();

        let mut act_var = Variable::new();
        for word in vec!["trireg", "4", "e", "my_reference"] {
            act_var.append(word, 0).unwrap();
        }
        assert_eq!(exp_var, act_var);
        assert!(act_var.is_done());
    }

    #[test]
    fn invalid_var_type_throws_error() {
        let mut act_var = Variable::new();
        let err = act_var.append("NotAVarType", 0).err();
        let exp_err = LoadError::InvalidParameterForCommand {
            line: 0,
            command: "$var".to_string(),
            parameter: "NotAVarType".to_string(),
        };
        assert_eq!(err, Some(exp_err))
    }

    #[test]
    fn non_digit_bit_width_throws_error() {
        let mut act_var = Variable::new();
        act_var.append("wire", 0).unwrap();
        let err = act_var.append("NotADigit", 0).err();
        let exp_err = LoadError::InvalidParameterForCommand {
            line: 0,
            command: "$var".to_string(),
            parameter: "NotADigit".to_string(),
        };
        assert_eq!(err, Some(exp_err));
    }

    #[test]
    fn extra_params_in_var_throws_error() {
        let mut act_var = Variable::new();
        for word in vec!["wire", "8", "e", "my_reference"] {
            act_var.append(word, 0).unwrap();
        }
        let err = act_var.append("ExtraParam", 0).err();
        let exp_err = LoadError::TooManyParameters {
            line: 0,
            command: "var".to_string(),
        };
        assert_eq!(err, Some(exp_err));
    }
}