netlist_db/parser/
data_sweep.rs

1use core::fmt;
2use std::{
3    borrow::Cow,
4    collections::HashMap,
5    hash::{Hash, Hasher},
6    path::PathBuf,
7};
8
9use nom::{
10    IResult, Parser,
11    branch::alt,
12    bytes::complete::{tag, take, take_until},
13    character::complete::char,
14    combinator::{map, map_res, opt},
15    multi::many1,
16    sequence::preceded,
17};
18use tokio::fs::read_to_string;
19
20use crate::{err::ParseError, span::LocatedSpan};
21
22use super::utils::{name_str, space_newline};
23
24#[derive(Debug, PartialEq, Eq, Hash)]
25pub enum DataType {
26    V,
27    I,
28    P,
29}
30#[derive(Debug)]
31pub struct DataName {
32    r#type: DataType,
33    name: Cow<'static, str>,
34}
35
36impl fmt::Display for DataName {
37    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
38        write!(
39            f,
40            "{}({})",
41            match self.r#type {
42                DataType::V => 'V',
43                DataType::I => 'I',
44                DataType::P => 'P',
45            },
46            self.name
47        )
48    }
49}
50
51impl DataName {
52    #[inline]
53    pub fn new_volt(name: Cow<'static, str>) -> Self {
54        Self {
55            r#type: DataType::V,
56            name,
57        }
58    }
59    #[inline]
60    pub fn new_current(name: Cow<'static, str>) -> Self {
61        Self {
62            r#type: DataType::I,
63            name,
64        }
65    }
66    #[inline]
67    pub fn new_param(name: Cow<'static, str>) -> Self {
68        Self {
69            r#type: DataType::P,
70            name,
71        }
72    }
73}
74
75impl PartialEq for DataName {
76    fn eq(&self, other: &Self) -> bool {
77        self.r#type.eq(&other.r#type) && self.name.to_lowercase().eq(&other.name.to_lowercase())
78    }
79}
80impl Eq for DataName {}
81
82impl Hash for DataName {
83    fn hash<H: Hasher>(&self, state: &mut H) {
84        self.r#type.hash(state);
85        self.name.to_lowercase().hash(state);
86    }
87}
88
89#[inline]
90fn data_type(i: LocatedSpan) -> IResult<LocatedSpan, DataType> {
91    alt((
92        map(char('v'), |_| DataType::V),
93        map(char('i'), |_| DataType::I),
94        map(char('p'), |_| DataType::P),
95    ))
96    .parse_complete(i)
97}
98
99#[inline]
100fn data_name(i: LocatedSpan) -> IResult<LocatedSpan, DataName> {
101    map(
102        (
103            data_type,
104            char('('),
105            (name_str, opt(preceded(char('\n'), name_str))),
106        ),
107        |(r#type, _, ((s1, _), n2))| DataName {
108            r#type,
109            name: if let Some((s2, _)) = n2 {
110                s1.to_owned() + s2
111            } else {
112                s1.to_owned()
113            }
114            .into(),
115        },
116    )
117    .parse_complete(i)
118}
119#[inline]
120fn float(i: LocatedSpan) -> IResult<LocatedSpan, f64> {
121    map_res(take(13u32), |s: LocatedSpan| {
122        fast_float2::parse(s.fragment().as_bytes())
123    })
124    .parse_complete(i)
125}
126
127const SWEEP_FLAG: &str = "sweep";
128const TERMINATION: &str = "$&%#";
129
130#[inline]
131pub async fn data_sweep(path: PathBuf) -> Result<HashMap<DataName, Vec<f64>>, ()> {
132    match read_to_string(&path).await {
133        Ok(s) => {
134            let (_, out) = data_sweep_nom(s.as_str().into()).map_err(|e| {
135                let err: ParseError = e.into();
136                err.report(&mut true, &crate::FileId::Include { path }, &s);
137            })?;
138            Ok(out)
139        }
140        Err(e) => {
141            let err: ParseError = e.into();
142            err.report(&mut true, &crate::FileId::Include { path }, "");
143            Err(())
144        }
145    }
146}
147
148#[inline]
149fn data_sweep_nom(i: LocatedSpan) -> IResult<LocatedSpan, HashMap<DataName, Vec<f64>>> {
150    map(
151        (
152            take_until(SWEEP_FLAG),
153            take(SWEEP_FLAG.len()),
154            many1(preceded(space_newline, data_name)),
155            space_newline,
156            tag(TERMINATION),
157            space_newline,
158            many1(preceded(space_newline, float)),
159        ),
160        |(_, _, names, _, _, _, values)| {
161            let name_len = names.len() + 1;
162            let size = values.len() / name_len;
163            names
164                .into_iter()
165                .enumerate()
166                .map(|(name_idx, n)| {
167                    (
168                        n,
169                        (0..size)
170                            .map(|i| values[i * name_len + name_idx + 1])
171                            .collect(),
172                    )
173                })
174                .collect()
175        },
176    )
177    .parse_complete(i)
178}
179
180#[tokio::test]
181async fn sim_sw0() {
182    #[cfg(not(feature = "tracing"))]
183    {
184        _ = simple_logger::SimpleLogger::new().init();
185    }
186    #[cfg(feature = "tracing")]
187    {
188        let subscriber = tracing_subscriber::FmtSubscriber::builder()
189            // .with_ansi(colored::control::SHOULD_COLORIZE.should_colorize())
190            .with_max_level(tracing::Level::DEBUG)
191            .with_target(false)
192            .with_file(true)
193            .with_line_number(true)
194            .with_timer(tracing_subscriber::fmt::time::ChronoLocal::new(
195                "%FT%T".to_owned(),
196            ))
197            .finish();
198        _ = tracing::subscriber::set_global_default(subscriber);
199    }
200    const DATA: &str = include_str!("../../tests/sim.sw0");
201    _ = dbg!(data_sweep_nom(DATA.into()));
202    _ = dbg!(data_sweep("tests/sim.sw0".into()).await);
203}