1#![expect(clippy::upper_case_acronyms)]
2extern crate alloc;
3
4mod _impl_display;
5mod builder;
6mod err;
7pub mod instance;
8pub mod parser;
9
10use alloc::borrow::Cow;
11use builder::{
12 Builder as _,
13 span::{FileId, ParsedId},
14};
15use err::{ParseError, ParseErrorInner};
16use std::collections::HashMap;
17
18#[derive(Debug)]
19pub struct Parsed {
20 pub top_id: ParsedId,
21 pub id2idx: HashMap<FileId, ParsedId>,
22 pub inner: Vec<(FileId, builder::AST)>,
23}
24#[derive(Debug)]
25pub struct Files {
26 pub inner: Vec<String>,
27}
28
29#[derive(Debug, Clone)]
30pub enum Value<'s> {
31 Num(f64),
32 Expr(Cow<'s, str>),
33}
34
35#[derive(Debug, Clone)]
36pub struct KeyValue<'s> {
37 pub k: Cow<'s, str>,
38 pub v: Value<'s>,
39}
40
41#[derive(Debug, Clone)]
42pub enum Token<'s> {
43 KV(KeyValue<'s>),
44 Value(Value<'s>),
45 V(Cow<'s, str>),
46 I(Cow<'s, str>),
47}
48
49#[derive(Debug, Clone)]
56pub struct Subckt<'s> {
57 pub name: Cow<'s, str>,
58 pub ports: Vec<Cow<'s, str>>,
60 pub params: Vec<KeyValue<'s>>,
61 pub ast: AST<'s>,
62}
63
64#[derive(Debug, Clone)]
65pub struct General<'s> {
66 pub cmd: builder::GeneralCmd,
67 pub tokens: Vec<Token<'s>>,
68}
69
70#[derive(Debug, Clone)]
71pub struct Unknwon<'s> {
72 pub cmd: Cow<'s, str>,
73 pub tokens: Vec<Token<'s>>,
74}
75
76#[derive(Debug, Clone)]
77pub struct Model<'s> {
78 pub name: Cow<'s, str>,
79 pub model_type: ModelType<'s>,
80 pub params: Vec<KeyValue<'s>>,
81}
82
83#[derive(Debug, Clone)]
84pub struct Data<'s> {
85 pub name: Cow<'s, str>,
86 pub values: DataValues<'s>,
87}
88
89#[derive(Debug, Clone)]
90pub enum DataValues<'s> {
91 InlineExpr {
92 params: Vec<Cow<'s, str>>,
93 values: Vec<Value<'s>>,
94 },
95 InlineNum {
96 params: Vec<Cow<'s, str>>,
97 values: Vec<f64>,
98 },
99 MER(DataFiles<'s>),
102 LAM(DataFiles<'s>),
104}
105#[derive(Debug, Clone)]
106pub struct DataFile<'s> {
107 pub file: Cow<'s, str>,
108 pub pname_col_num: Vec<PnameColNum<'s>>,
109}
110
111#[derive(Debug, Clone)]
112pub struct PnameColNum<'s> {
113 pub pname: Cow<'s, str>,
114 pub col_num: usize,
115}
116#[derive(Debug, Clone)]
117pub struct DataFiles<'s> {
118 pub files: Vec<DataFile<'s>>,
119 pub out: Option<Cow<'s, str>>,
120}
121#[cfg(feature = "py")]
122use polars::{error::PolarsError, frame::DataFrame, prelude::Column};
123pub struct DataValuesCsv<'s, 'a>(pub(crate) &'a DataValues<'s>);
124impl<'s> DataValues<'s> {
125 pub fn csv(&self) -> DataValuesCsv<'s, '_> {
126 DataValuesCsv(self)
127 }
128 #[cfg(feature = "py")]
129 pub fn dataframe(&self) -> Result<DataFrame, PolarsError> {
130 if let Self::InlineNum { params, values } = self {
131 let ncols = params.len();
132 if ncols == 0 {
133 return Err(PolarsError::ComputeError("Header is empty".into()));
134 }
135 if values.len() % ncols != 0 {
136 return Err(PolarsError::ComputeError(
137 "Data length is not a multiple of the number of columns".into(),
138 ));
139 }
140 let nrows = values.len() / ncols;
141 let columns = params
142 .into_iter()
143 .enumerate()
144 .map(|(col_idx, name)| {
145 Column::new(
146 name.as_ref().into(),
147 (0..nrows)
148 .into_iter()
149 .map(|row| values[row * ncols + col_idx])
150 .collect::<Vec<f64>>(),
151 )
152 })
153 .collect();
154 DataFrame::new(columns)
155 } else {
156 Err(PolarsError::ComputeError("Is not inline data".into()))
157 }
158 }
159}
160
161#[derive(Debug, Clone)]
162pub enum ModelType<'s> {
163 AMP,
165 C,
167 CORE,
169 D,
171 L,
173 NJF,
175 NMOS,
177 NPN,
179 OPT,
181 PJF,
183 PMOS,
185 PNP,
187 R,
189 U,
191 W,
193 S,
195 Unknown(Cow<'s, str>),
196}
197
198#[derive(Debug, Clone, Default)]
199pub struct AST<'s> {
200 pub subckt: Vec<Subckt<'s>>,
201 pub instance: Vec<instance::Instance<'s>>,
202 pub model: Vec<Model<'s>>,
203 pub param: Vec<KeyValue<'s>>,
204 pub option: Vec<(Cow<'s, str>, Option<Value<'s>>)>,
205 pub init_condition: Vec<(Cow<'s, str>, Value<'s>, Option<Cow<'s, str>>)>,
210 pub nodeset: Vec<(Cow<'s, str>, Value<'s>, Option<Cow<'s, str>>)>,
215 pub general: Vec<General<'s>>,
216 pub data: Vec<Data<'s>>,
217 pub unknwon: Vec<Unknwon<'s>>,
218}
219
220impl builder::AST {
221 #[expect(clippy::too_many_arguments)]
222 fn build<'s>(
223 &self,
224 ast: &mut AST<'s>,
225 has_err: &mut bool,
226 file_id: &FileId,
227 parsed_id: ParsedId,
228 files: &'s Files,
229 parsed: &Parsed,
230 ) {
231 fn build_local<'s>(
232 local_ast: &builder::LocalAST,
233 ast: &mut AST<'s>,
234 has_err: &mut bool,
235 file: &'s str,
236 file_id: &FileId,
237 parsed_id: ParsedId,
238 files: &'s Files,
239 parsed: &Parsed,
240 ) {
241 fn build_subckt<'s>(
242 s: &builder::Subckt,
243 has_err: &mut bool,
244 file: &'s str,
245 file_id: &FileId,
246 parsed_id: ParsedId,
247 files: &'s Files,
248 parsed: &Parsed,
249 ) -> Subckt<'s> {
250 let mut ast = AST::default();
251 s.ast
252 .build(&mut ast, has_err, file_id, parsed_id, files, parsed);
253 Subckt {
254 name: s.name.build(file),
255 ports: s.ports.build(file),
256 params: s.params.build(file),
257 ast,
258 }
259 }
260 ast.subckt.extend(
261 local_ast
262 .subckt
263 .iter()
264 .map(|s| build_subckt(s, has_err, file, file_id, parsed_id, files, parsed)),
265 );
266 ast.instance
267 .extend(local_ast.instance.iter().map(|b| b.build(file)));
268 ast.model
269 .extend(local_ast.model.iter().map(|b| b.build(file)));
270 ast.param
271 .extend(local_ast.param.iter().map(|b| b.build(file)));
272 ast.option
273 .extend(local_ast.option.iter().map(|b| b.build(file)));
274 ast.general
275 .extend(local_ast.general.iter().map(|b| b.build(file)));
276 ast.data
277 .extend(local_ast.data.iter().map(|b| b.build(file)));
278 ast.init_condition
279 .extend(local_ast.init_condition.iter().map(|b| b.build(file)));
280 ast.nodeset
281 .extend(local_ast.nodeset.iter().map(|b| b.build(file)));
282 ast.unknwon
283 .extend(local_ast.unknwon.iter().map(|b| b.build(file)));
284 for e in &local_ast.errors {
285 e.report(has_err, file_id, file);
286 }
287 }
288 let file = &files.inner[parsed_id.0];
289 for seg in &self.segments {
290 match seg {
291 builder::Segment::Local(local_ast) => {
292 build_local(
293 local_ast, ast, has_err, file, file_id, parsed_id, files, parsed,
294 );
295 }
296 builder::Segment::Include(ast_res) => {
297 let ast_res = ast_res.get().unwrap();
298 match ast_res {
299 Ok(parsed_id) => {
300 let (file_id, _ast) = &parsed.inner[parsed_id.0];
301 _ast.build(ast, has_err, file_id, *parsed_id, files, parsed);
302 }
303 Err(e) => {
304 e.report(has_err, file_id, file);
305 }
306 }
307 }
308 }
309 }
310 }
311}
312
313impl Files {
314 #[inline]
315 pub fn build(&self, parsed: Parsed) -> (AST<'_>, bool) {
316 let mut ast = AST::default();
317 let mut has_err = false;
318 let (file_id, _ast) = &parsed.inner[parsed.top_id.0];
319 _ast.build(
320 &mut ast,
321 &mut has_err,
322 file_id,
323 parsed.top_id,
324 self,
325 &parsed,
326 );
327 (ast, has_err)
328 }
329}