ngspice-parser 0.1.1

A simple parser to parse ngspice raw files
Documentation
use serde::Serialize;
#[derive(Debug, Clone, Copy, Serialize)]
pub enum Flags {
    Complex,
    Real,
}
#[derive(Debug, Serialize)]
pub struct VarData {
    pub name: String,
    pub typee: String,
    pub values: Vec<f64>,
    pub angles: Option<Vec<f64>>,
}
#[derive(Debug, Serialize)]
pub struct Plot {
    pub title: String,
    pub date: String,
    pub plotname: String,
    pub flags: Flags,
    pub no_of_variables: usize,
    pub no_of_points: usize,
    pub data: Vec<VarData>,
}

#[derive(thiserror::Error, Debug)]
pub enum SpiceParseError {
    #[error("Cannot parse integer")]
    ParseInt(#[from] std::num::ParseIntError),
    #[error("Cannot parse float")]
    ParseFloat(#[from] std::num::ParseFloatError),
    #[error("Number of variables mismatch")]
    NoOfVarMismatch,
    #[error("Number of values mismatch")]
    NoOfValMismatch,
    #[error("Unknown value in flags")]
    UnknownFlag,
}
fn flush_values(
    no_of_variables: usize,
    temp_values: &mut Vec<(f64, f64)>,
    data: &mut Vec<VarData>,
    flags: Flags,
) -> Result<(), SpiceParseError> {
    if temp_values.len() != 0 {
        if temp_values.len() != no_of_variables {
            return Result::Err(SpiceParseError::NoOfValMismatch);
        }
        let mut idx: usize = 0;
        for val in temp_values.iter() {
            data[idx].values.push(val.0);
            if let Flags::Complex = flags {
                if let Option::Some(vec) = &mut data[idx].angles {
                    vec.push(val.1);
                }
            }
            idx += 1;
        }
        temp_values.clear();
    }
    Ok(())
}
pub fn parse(file: &str) -> Result<Plot, SpiceParseError> {
    let mut title: String = String::new();
    let mut date: String = String::new();
    let mut plotname: String = String::new();
    let mut flags: Flags = Flags::Real;
    let mut no_of_variables: usize = 0;
    let mut no_of_points: usize = 0;
    let mut data: Vec<VarData> = Vec::new();
    enum Modes {
        Meta,
        Variable,
        Value,
    }
    let mut mode: Modes = Modes::Meta;
    let mut variable_counter: usize = 0;
    let mut temp_values: Vec<(f64, f64)> = Vec::new();
    for lin in file.lines() {
        if lin.trim().len() == 0 {
            continue;
        }
        match mode {
            Modes::Meta => {
                let parts: Vec<&str> = lin.trim().split(':').collect();
                match parts[0] {
                    "Title" => title = String::from(parts[1].trim()),
                    "Date" => date = String::from(parts[1..].join("").trim()),
                    "Plotname" => plotname = String::from(parts[1].trim()),
                    "Flags" => {
                        flags = match parts[1].trim() {
                            "complex" => Flags::Complex,
                            "real" => Flags::Real,
                            _ => {
                                return Result::Err(SpiceParseError::UnknownFlag);
                            }
                        }
                    }
                    "No. Variables" => no_of_variables = parts[1].trim().parse()?,
                    "No. Points" => no_of_points = parts[1].trim().parse()?,
                    "Variables" => mode = Modes::Variable,
                    "Values" => mode = Modes::Value,
                    _ => {}
                };
            }
            Modes::Variable => {
                if variable_counter == no_of_variables {
                    return Result::Err(SpiceParseError::NoOfVarMismatch);
                }
                variable_counter += 1;

                if variable_counter == no_of_variables {
                    mode = Modes::Meta;
                }
                let parts: Vec<&str> = lin.trim().split("\t").collect();
                data.push(VarData {
                    name: String::from(parts[1].trim()),
                    typee: String::from(parts[2].trim()),
                    values: Vec::new(),
                    angles: match flags {
                        Flags::Real => None,
                        Flags::Complex => Some(Vec::new()),
                    },
                })
            }
            Modes::Value => {
                let parts: Vec<&str> = lin.trim().split('\t').collect();
                let mut num = parts[0];
                if parts.len() == 2 {
                    flush_values(no_of_variables, &mut temp_values, &mut data, flags)?;
                    num = parts[1];
                };
                let val = match flags {
                    Flags::Real => (num.parse()?, 0f64),
                    Flags::Complex => {
                        let pts: Vec<&str> = num.split(",").collect();
                        let real: f64 = pts[0].parse()?;
                        let imaginary: f64 = pts[1].parse()?;
                        (
                            (real.powi(2) + imaginary.powi(2)).sqrt(),
                            (imaginary / real).atan(),
                        )
                    }
                };
                temp_values.push(val);
            }
        };
    }
    flush_values(no_of_variables, &mut temp_values, &mut data, flags)?;
    Result::Ok(Plot {
        title,
        date,
        plotname,
        flags,
        no_of_variables,
        no_of_points,
        data,
    })
}
pub fn parse_and_get_csv(file: &str) -> Result<String, SpiceParseError> {
    let mut ret = String::new();
    let plot = parse(file)?;
    for var_data in plot.data.iter() {
        ret += var_data.name.as_str();
        ret += " - ";
        ret += var_data.typee.as_str();
        ret += ",";
        if let Flags::Complex = plot.flags {
            ret += var_data.typee.as_str();
            ret += "(phase),";
        }
    }
    ret.remove(ret.len() - 1);
    ret += "\n";
    for i in 0..plot.no_of_points {
        for j in 0..plot.no_of_variables {
            let val: String = match plot.flags {
                Flags::Real => plot.data[j].values[i].to_string(),
                Flags::Complex => {
                    if let Some(angles) = &plot.data[j].angles {
                        let mut a = plot.data[j].values[i].to_string();
                        a += ",";
                        a += angles[i].to_degrees().to_string().as_str();
                        a += "°";
                        a
                    } else {
                        String::from("")
                    }
                }
            };
            ret += val.as_str();
            ret += if j != (plot.no_of_variables - 1) {
                ","
            } else {
                "\n"
            };
        }
    }
    Ok(ret)
}
#[cfg(test)]
pub mod tests;