ngspice_parser/
lib.rs

1use serde::Serialize;
2#[derive(Debug, Clone, Copy, Serialize)]
3pub enum Flags {
4    Complex,
5    Real,
6}
7#[derive(Debug, Serialize)]
8pub struct VarData {
9    pub name: String,
10    pub typee: String,
11    pub values: Vec<f64>,
12    pub angles: Option<Vec<f64>>,
13}
14#[derive(Debug, Serialize)]
15pub struct Plot {
16    pub title: String,
17    pub date: String,
18    pub plotname: String,
19    pub flags: Flags,
20    pub no_of_variables: usize,
21    pub no_of_points: usize,
22    pub data: Vec<VarData>,
23}
24
25#[derive(thiserror::Error, Debug)]
26pub enum SpiceParseError {
27    #[error("Cannot parse integer")]
28    ParseInt(#[from] std::num::ParseIntError),
29    #[error("Cannot parse float")]
30    ParseFloat(#[from] std::num::ParseFloatError),
31    #[error("Number of variables mismatch")]
32    NoOfVarMismatch,
33    #[error("Number of values mismatch")]
34    NoOfValMismatch,
35    #[error("Unknown value in flags")]
36    UnknownFlag,
37}
38fn flush_values(
39    no_of_variables: usize,
40    temp_values: &mut Vec<(f64, f64)>,
41    data: &mut Vec<VarData>,
42    flags: Flags,
43) -> Result<(), SpiceParseError> {
44    if temp_values.len() != 0 {
45        if temp_values.len() != no_of_variables {
46            return Result::Err(SpiceParseError::NoOfValMismatch);
47        }
48        let mut idx: usize = 0;
49        for val in temp_values.iter() {
50            data[idx].values.push(val.0);
51            if let Flags::Complex = flags {
52                if let Option::Some(vec) = &mut data[idx].angles {
53                    vec.push(val.1);
54                }
55            }
56            idx += 1;
57        }
58        temp_values.clear();
59    }
60    Ok(())
61}
62pub fn parse(file: &str) -> Result<Plot, SpiceParseError> {
63    let mut title: String = String::new();
64    let mut date: String = String::new();
65    let mut plotname: String = String::new();
66    let mut flags: Flags = Flags::Real;
67    let mut no_of_variables: usize = 0;
68    let mut no_of_points: usize = 0;
69    let mut data: Vec<VarData> = Vec::new();
70    enum Modes {
71        Meta,
72        Variable,
73        Value,
74    }
75    let mut mode: Modes = Modes::Meta;
76    let mut variable_counter: usize = 0;
77    let mut temp_values: Vec<(f64, f64)> = Vec::new();
78    for lin in file.lines() {
79        if lin.trim().len() == 0 {
80            continue;
81        }
82        match mode {
83            Modes::Meta => {
84                let parts: Vec<&str> = lin.trim().split(':').collect();
85                match parts[0] {
86                    "Title" => title = String::from(parts[1].trim()),
87                    "Date" => date = String::from(parts[1..].join("").trim()),
88                    "Plotname" => plotname = String::from(parts[1].trim()),
89                    "Flags" => {
90                        flags = match parts[1].trim() {
91                            "complex" => Flags::Complex,
92                            "real" => Flags::Real,
93                            _ => {
94                                return Result::Err(SpiceParseError::UnknownFlag);
95                            }
96                        }
97                    }
98                    "No. Variables" => no_of_variables = parts[1].trim().parse()?,
99                    "No. Points" => no_of_points = parts[1].trim().parse()?,
100                    "Variables" => mode = Modes::Variable,
101                    "Values" => mode = Modes::Value,
102                    _ => {}
103                };
104            }
105            Modes::Variable => {
106                if variable_counter == no_of_variables {
107                    return Result::Err(SpiceParseError::NoOfVarMismatch);
108                }
109                variable_counter += 1;
110
111                if variable_counter == no_of_variables {
112                    mode = Modes::Meta;
113                }
114                let parts: Vec<&str> = lin.trim().split("\t").collect();
115                data.push(VarData {
116                    name: String::from(parts[1].trim()),
117                    typee: String::from(parts[2].trim()),
118                    values: Vec::new(),
119                    angles: match flags {
120                        Flags::Real => None,
121                        Flags::Complex => Some(Vec::new()),
122                    },
123                })
124            }
125            Modes::Value => {
126                let parts: Vec<&str> = lin.trim().split('\t').collect();
127                let mut num = parts[0];
128                if parts.len() == 2 {
129                    flush_values(no_of_variables, &mut temp_values, &mut data, flags)?;
130                    num = parts[1];
131                };
132                let val = match flags {
133                    Flags::Real => (num.parse()?, 0f64),
134                    Flags::Complex => {
135                        let pts: Vec<&str> = num.split(",").collect();
136                        let real: f64 = pts[0].parse()?;
137                        let imaginary: f64 = pts[1].parse()?;
138                        (
139                            (real.powi(2) + imaginary.powi(2)).sqrt(),
140                            (imaginary / real).atan(),
141                        )
142                    }
143                };
144                temp_values.push(val);
145            }
146        };
147    }
148    flush_values(no_of_variables, &mut temp_values, &mut data, flags)?;
149    Result::Ok(Plot {
150        title,
151        date,
152        plotname,
153        flags,
154        no_of_variables,
155        no_of_points,
156        data,
157    })
158}
159pub fn parse_and_get_csv(file: &str) -> Result<String, SpiceParseError> {
160    let mut ret = String::new();
161    let plot = parse(file)?;
162    for var_data in plot.data.iter() {
163        ret += var_data.name.as_str();
164        ret += " - ";
165        ret += var_data.typee.as_str();
166        ret += ",";
167        if let Flags::Complex = plot.flags {
168            ret += var_data.typee.as_str();
169            ret += "(phase),";
170        }
171    }
172    ret.remove(ret.len() - 1);
173    ret += "\n";
174    for i in 0..plot.no_of_points {
175        for j in 0..plot.no_of_variables {
176            let val: String = match plot.flags {
177                Flags::Real => plot.data[j].values[i].to_string(),
178                Flags::Complex => {
179                    if let Some(angles) = &plot.data[j].angles {
180                        let mut a = plot.data[j].values[i].to_string();
181                        a += ",";
182                        a += angles[i].to_degrees().to_string().as_str();
183                        a += "°";
184                        a
185                    } else {
186                        String::from("")
187                    }
188                }
189            };
190            ret += val.as_str();
191            ret += if j != (plot.no_of_variables - 1) {
192                ","
193            } else {
194                "\n"
195            };
196        }
197    }
198    Ok(ret)
199}
200#[cfg(test)]
201pub mod tests;