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_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}